Livestreaming web audio and video - Web media technologies

Livestreaming web audio and video
=================================

Livestreaming technology is often employed to relay live events such as sports, concerts and more generally TV and Radio programmes that are output live. Often shortened to just streaming, livestreaming is the process of transmitting media 'live' to computers and devices. This is a fairly complex and nascent subject with a lot of variables, so in this article, we'll introduce you to the subject and let you know how you can get started.

The key consideration when streaming media to a browser is the fact that rather than playing a finite file we are relaying a file that is being created on the fly and has no pre-determined start or end.

Key differences between streamed and static media
-------------------------------------------------

In this case, we are using static media to describe media that is represented by a file, whether it be an mp3 or WebM file. This file sits on a server and can be delivered — like most other files — to the browser. This is often known as a progressive download.

Livestreamed media lacks a finite start and end time rather than a static file, it is a stream of data that the server passes on down the line to the browser and is often adaptive (see below). Usually, we require different formats and special server-side software to achieve this.

Adaptive streaming
------------------

One of the main priorities for livestreaming is to keep the player synchronized with the stream: adaptive streaming is a technique for doing this in the case of low bandwidth. The idea is that the data transfer rate is monitored and if it looks like it's not keeping up, we drop down to a lower bandwidth (and consequently lower quality) stream. In order to have this capability, we need to use formats that facilitate this. Livestreaming formats generally allow adaptive streaming by breaking streams into a series of small segments and making those segments available at different qualities and bit rates.

Streaming Audio and Video on Demand
-----------------------------------

Streaming technology is not used exclusively for live streams. It can also be used instead of the traditional progressive download method for Audio and Video on demand:

There are several advantages to this:

* Latency is generally lower so media will start playing more quickly
* Adaptive streaming makes for better experiences on a variety of devices
* Media is downloaded just in time which makes bandwidth usage more efficient
Streaming Protocols
-------------------

While static media is usually served over HTTP, there are several protocols for serving adaptive streams; let's take a look at the options.

### HTTP

For now, HTTP is by far the most commonly supported protocol used to transfer media on demand or live.

### RTMP

Real Time Messaging Protocol (RTMP) is a proprietary protocol developed by Macromedia (now Adobe) and supported by the Adobe Flash plugin. RTMP comes in various flavors including RTMPE (Encrypted), RTMPS (Secure over TLS/SSL) and RTMPT (encapsulated within HTTP requests).

### RTSP

**Note:** Real Time Streaming Protocol (RTSP) controls media sessions between endpoints and is often used together with Real-time Transport Protocol (RTP) and with Real-time Control Protocol (RTCP) for media stream delivery. Using RTP with RTCP allows for adaptive streaming. This is not yet supported natively in most browsers.

Some vendors implement propriety transport protocols, such as RealNetworks and their Real Data Transport (RDT).

### RTSP 2.0

RTSP 2.0 is currently in development and is not backward compatible with RTSP 1.0.

**Warning:** Although the `<audio>` and `<video>` tags are protocol agnostic, no browser currently supports anything other than HTTP without requiring plugins, although this looks set to change. Protocols other than HTTP may also be subject to blocking from firewalls or proxy servers.

Using streaming protocols
-------------------------

The process of using the various protocols is reassuringly familiar if you are used to working with media over HTTP.

For example:

```html
<video src="rtsp://myhost.com/mymedia.format">
  <!-- Fallback here -->
</video>
```

Media Source Extensions (MSE)
-----------------------------

Media Source Extensions is a W3C working draft that plans to extend `HTMLMediaElement` to allow JavaScript to generate media streams for playback. Allowing JavaScript to generate streams facilitates a variety of use cases like adaptive streaming and time shifting live streams.

For example, you could implement MPEG-DASH using JavaScript while offloading the decoding to MSE.

**Note:** Time Shifting is the process of consuming a live stream sometime after it happened.

Video Streaming File Formats
----------------------------

A couple of HTTP-based livestreaming video formats are beginning to see support across browsers.

**Note:** You can find a guide to encoding HLS and MPEG-DASH for use on the web at Setting up adaptive streaming media sources.

### MPEG-DASH

DASH stands for Dynamic Adaptive Streaming over HTTP. It is supported via Media Source Extensions which are used by JavaScript libraries such as DASH.js. This approach allows us to download chunks of the video stream using XHR and "append" the chunks to the stream that's played by the `<video>` element. So for example, if we detect that the network is slow, we can start requesting lower quality (smaller) chunks for the next segment. This technology also allows an advertising segment to be appended/inserted into the stream.

**Note:** You can also use WebM with the MPEG DASH adaptive streaming system.

### HLS

HLS or HTTP Live Streaming is a protocol invented by Apple Inc and supported on iOS, Safari and the latest versions of Android browser / Chrome. HLS is also adaptive.

HLS can also be decoded using JavaScript, which means we can support the latest versions of Firefox, Chrome and Safari. See this HTTP Live Streaming JavaScript player.

At the start of the streaming session, an extended M3U (m3u8) playlist is downloaded. This contains the metadata for the various sub-streams that are provided.

### Streaming File Format Support

| Browser | DASH | HLS | Opus (Audio) |
| --- | --- | --- | --- |
| Firefox 32 | ✓ [1] | ✓ [2] | ✓ 14+ |
| Safari 6+ |  | ✓ |  |
| Chrome 24+ | ✓ [1] | ✓ |  |
| Opera 20+ | ✓ [1] |  |  |
| Internet Explorer 10+ | ✓ 11 | ✓ [2] |  |
| Firefox Mobile | ✓ | ✓ | ✓ |
| Safari iOS6+ |  | ✓ |  |
| Chrome Mobile | ✓ | ✓ [2] |  |
| Opera Mobile | ✓ [1] | ✓ |  |
| Android | ✓ |  |  |

[1] Via JavaScript and MSE

[2] Via JavaScript and a CORS Proxy

Audio Streaming File Formats
----------------------------

There are also some audio formats beginning to see support across browsers.

### Opus

Opus is a royalty-free and open format that manages to optimize quality at various bit-rates for different types of audio. Music and speech can be optimized in different ways and Opus uses the SILK and CELT codecs to achieve this.

Currently, Opus is supported by Firefox desktop and mobile as well as the latest versions of desktop Chrome and Opera.

**Note:** Opus is a mandatory format for WebRTC browser implementations.

### MP3, AAC, Ogg Vorbis

Most common audio formats can be streamed using specific server-side technologies.

Note: It's potentially easier to stream audio using non-streaming formats because unlike video there are no keyframes.

Server-side Streaming Technologies
----------------------------------

In order to stream live audio and video, you will need to run specific streaming software on your server or use third-party services.

### GStreamer

GStreamer is an open source cross-platform multimedia framework that allows you to create a variety of media-handling components, including streaming components. Through its plugin system, GStreamer provides support for more than a hundred codecs (including MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, and FLV.)

GStreamer plugins such as souphttpclientsink and shout2send exist to stream media over HTTP or you can also integrate with Python's Twisted framework.

For RTMP transfer you can use the Nginx RTMP Module.

### SHOUTcast

SHOUTcast is a cross-platform proprietary technology for streaming media. Developed by Nullsoft, it allows digital audio content in MP3 or AAC format to be broadcast. For web use, SHOUTcast streams are transmitted over HTTP.

**Note:** SHOUTcast URLs may require a semicolon to be appended to them.

### Icecast

The Icecast server is an open source technology for streaming media. Maintained by the Xiph.org Foundation, it streams Ogg Vorbis/Theora as well as MP3 and AAC format via the SHOUTcast protocol.

**Note:** SHOUTcast and Icecast are among the most established and popular technologies, but there are many more streaming media systems available.

### Streaming Services

Although you can install software like GStreamer, SHOUTcast and Icecast you will also find a lot of third-party streaming services that will do much of the work for you.

See also
--------

* HTTP Live Streaming
* HLS Browser Support
* HTTP Live Streaming JavaScript player
* The Basics of HTTP Live Streaming
* DASH Adaptive Streaming for HTML 5 Video
* Dynamic Adaptive Streaming over HTTP (MPEG-DASH)
* MPEG-DASH Media Source Demo
* DASH Reference Client
* Dynamic Streaming over HTTP
* The State of MPEG-DASH Deployment
* Look, no plugins: Live streaming to the browser using Media Source Extensions and MPEG-DASH
* Media Source Extensions (W3C)
* Icecast
* SHOUTcast
* GStreamer
* Streaming GStreamer Pipelines Via HTTP
* GStreamer and Raspberry Pi
* Comparison of Streaming Media Systems
* Mozilla Hacks - Streaming Media on demand with Media Source Extensions


Cross-browser audio basics - Web media technologies

Cross-browser audio basics
==========================

This article provides:

* a basic guide to creating a cross-browser HTML audio player with all the associated attributes, properties, and events explained
* a guide to custom controls created using the Media API
Basic Audio example
-------------------

The code below is an example of a basic audio implementation using HTML5:

```html
<audio controls>
  <source src="audiofile.mp3" type="audio/mpeg" />
  <source src="audiofile.ogg" type="audio/ogg" />
  <!-- fallback for non-supporting browsers goes here -->
  <p>
    Your browser does not support HTML audio, but you can still
    <a href="audiofile.mp3">download the music</a>.
  </p>
</audio>
```

**Note:** You can also use an MP4 file instead of MP3. MP4 files typically contain AAC encoded audio. You can use `type="audio/mp4"`. (Currently, browsers that support mp3 also support mp4 audio).

* Here we define an `<audio>` element with multiple sources — we do this as not all browsers support the same audio formats. To ensure reasonable coverage, we should specify at least two different formats. The two formats that will give maximum coverage are mp3 and ogg vorbis.
* We do this using the `<source>` element, which takes the attributes `src` and `type`.
    + `src` contains the path to the audio file to be loaded (relative or absolute).
    + `type` is used to inform the browser of the file type. If omitted, most browsers will attempt to guess this from the file extension.
* If the `<audio>` element is not supported then `<audio>` and `<source>` will be ignored. However, any supported text or elements that you define within `<audio>` will be displayed or acted upon. So the ideal place to create a fallback or inform of incompatibility is before the closing `</audio>` tag. In this case, we've provided a simple paragraph including a link to download the audio directly.
* The `controls` attribute on the `<audio>` element is specified when we require the browser to provide us with default playback controls. If you don't specify this attribute, no controls will appear — and you will instead have to create your own controls and program their functionality using the Media API (see below). However, that can be a good approach, because the default controls look different among various browsers. So creating your own controls ensures a consistent look for the controls across all browsers.
HTML audio in detail
--------------------

Now we've looked at a basic example, let's now explore the different aspects of HTML audio in more detail.

### Audio HTML attributes

We can specify a number of attributes with the audio tag to further determine the way audio is initialized.

#### autoplay

Specifying `autoplay` will cause the audio to start playing as soon as possible and without any user interaction — in short, the audio will autoplay.

```html
<audio autoplay>…</audio>
```

**Note:** This value is often ignored on mobile platforms, and its use is not recommended unless really necessary. Autoplaying audio (and video) is usually really annoying. Plus browsers have policies that will block autoplay entirely in many situations. See the Autoplay guide for media and Web Audio APIs for details.

#### loop

The `loop` attribute will ensure that upon getting to the end of the audio clip, the audio clip will loop back to the beginning and start playing again.

```html
<audio loop>…</audio>
```

#### muted

If you want the audio to start muted (no volume), add the `muted` attribute.

```html
<audio muted>…</audio>
```

**Note:** This value is often ignored on mobile platforms.

#### preload

The `preload` attribute allows you to specify a preference for how the browser preloads the audio, in other words, which part of the file it downloads when the `<audio>` element is initialized, and before the play button is pressed.

`preload` can take 3 different values:

1. `none`: Don't download anything before the play button is pressed.
2. `metadata`: Download the audio metadata; this is usually the best option, as it allows you to access and display information such as audio length, and allow the browser to work out which audio file it should use.
3. `auto`: Download the whole audio file as soon as possible. This is generally not a good option unless you can guarantee your users will have a fast network connection.

**Note:** This value is often ignored on mobile platforms.

```html
<audio preload="auto">…</audio>
```

#### controls

We specify the `controls` attribute when we require the browser to provide us with its default playback controls.

```html
<audio controls>…</audio>
```

#### src

As mentioned above, you can use the `<source>` element to specify one or more source audio files. Alternatively, you can include the `src` attribute directly on the `<audio>` element to specify a single source file.

```html
<audio src="audiofile.mp3">…</audio>
```

#### type

As mentioned above, to be sure that the browser knows what type of file is being specified, it's good practice to specify a `type` attribute alongside the `src` attribute. The `type` attribute specifies the MIME type or Internet Media Type of the file.

```html
<audio src="audiofile.mp3" type="audio/mpeg">…</audio>
```

### Manipulating the Audio Element with JavaScript

In addition to being able to specify various attributes in HTML, the `<audio>` element comes complete with several properties and methods that you can manipulate via JavaScript.

Given the following HTML:

```html
<audio id="my-audio" src="audiofile.mp3">…</audio>
```

You can grab the `<audio>` element like this:

```js
const audio = document.getElementById("my-audio");
```

Alternatively, you can create a new element. Here's an example of creating an `<audio>` element, setting the media to play, playing and pausing, and then playing from 5 seconds into the audio:

```js
const audio = document.createElement("audio");

if (audio.canPlayType("audio/mpeg")) {
  audio.setAttribute("src", "audiofile.mp3");
}

if (audio.canPlayType("audio/ogg")) {
  audio.setAttribute("src", "audiofile.ogg");
}

alert("play");

audio.play();

alert("stop");

audio.pause();

alert("play from 5 seconds in");

audio.currentTime = 5;
audio.play();
```

Let's explore the available properties and methods in more detail.

#### play

The `play()` method is used to tell the audio to play. It takes no parameters.

```js
audio.play();
```

#### pause

The `pause()` method is used to tell the audio to pause. It takes no parameters.

```js
audio.pause();
```

**Note:** There is no stop method — to implement a stop function, you'd have to pause the media then set the `currentTime` property value to 0.

#### canPlayType

The `canPlayType()` method asks the browser whether a certain audio file type is supported. It takes the mime type of the type to check as a parameter.

```js
if (audio.canPlayType("audio/mpeg")) {
  // It's supported.
  // Do something here!
}
```

`canPlayType()` returns one of three values:

1. `probably`
2. `maybe`
3. "" (an empty string)

In practice, we usually check if the result is true or false. Non-empty strings are true.

**Note:** A very early spec specified that the browser should return `no` instead of an empty string, but thankfully the number of people using older browsers that implement this version of the spec are few and far between.

#### currentTime

The `currentTime` property gets or sets the current time the audio should play at. This is useful in many ways, for example since `play()` does not take a parameter we need to set the point to play from separately if we don't want it to be 0.

The value of `currentTime` is a number which represents the time in seconds.

```js
if (audio.currentTime > 5) {
  audio.currentTime = 3;
}
```

#### volume

The `volume` property allows us to set the audio volume, as a number between 0 and 1.

```js
// set the volume at 50%
audio.volume = 0.5;
```

Creating your own custom audio player
-------------------------------------

The JavaScript media API allows you to create your own custom player. Let's take a look at a very minimal example. We can combine HTML and JavaScript to create a very simple player with a play and a pause button. First, we'll set up the audio in the HTML, without the `controls` attribute, since we are creating our own controls:

```html
<audio id="my-audio">
  <source src="audiofile.mp3" type="audio/mpeg" />
  <source src="audiofile.ogg" type="audio/ogg" />
  <!-- place fallback here as <audio> supporting browsers will ignore it -->
  <p>Download<a href="audiofile.mp3">audiofile.mp3</a></p>
</audio>

<!-- custom play and pause buttons -->
<button id="play">play</button>
<button id="pause">pause</button>
```

Next, we attach some functionality to the player using JavaScript:

```js
window.onload = () => {
  const audio = document.getElementById("my-audio");
  const play = document.getElementById("play");
  const pause = document.getElementById("pause");

  // associate functions with the 'onclick' events
  play.onclick = playAudio;
  pause.onclick = pauseAudio;

  function playAudio() {
    audio.play();
  }

  function pauseAudio() {
    audio.pause();
  }
};
```

Media loading events
--------------------

Above we have shown how you can create a very simple audio player, but what if we want to show progress, buffering and only activate the buttons when the media is ready to play? Fortunately, there are a number of events we can use to let our player know exactly what is happening.

First, let's take a look at the media loading process in order:

### loadstart

The `loadstart` event tells us that load process has started and the browser is connecting to the media.

```js
audio.addEventListener("loadstart", () => {
  //grabbing the file
});
```

### durationchange

If you just want to know as soon as the duration of your media is established, this is the event for you. This can be useful because the initial value for duration is `NaN` (Not a Number), which you probably don't want to display to your users.

```js
audio.addEventListener("durationchange", () => {
  //you can display the duration now
});
```

### loadedmetadata

Metadata can consist of more than just duration — if you want to wait for all the metadata to download before doing something, you can detect the `loadedmetadata` event.

```js
audio.addEventListener("loadedmetadata", () => {
  //you can display the duration now
});
```

### loadeddata

The `loadeddata` event is fired when the first bit of media arrives. The playhead is in position but not quite ready to play.

```js
audio.addEventListener("loadeddata", () => {
  //you could display the playhead now
});
```

### progress

The `progress` event indicates that the download of media is still in progress. It is good practice to display some kind of 'loader' at this point.

```js
audio.addEventListener("progress", () => {
  // you could let the user know the media is downloading
});
```

### canplay

`canplay` is a useful event to detect should you want to determine whether the media is ready to play. You could, for example, disable custom controls until this event occurs.

```js
audio.addEventListener("canplay", () => {
  //audio is ready to play
});
```

### canplaythrough

`canplaythrough` is similar to `canplay` but it lets you know that the media is ready to be played all the way through (that is to say that the file has completely downloaded, or it is estimated that it will download in time so that buffering stops do not occur).

```js
audio.addEventListener("canplaythrough", () => {
  //audio is ready to play all the way through
});
```

### Media loading event order

To recap, the order of the media loading events are:

`loadstart` > `durationchange` > `loadedmetadata` > `loadeddata` > `progress` > `canplay` > `canplaythrough`

### Loading interruption events

We also have a few events available that will fire when there is some kind of interruption to the media loading process.

suspend

Media data is no longer being fetched even though the file has not been entirely downloaded.

abort

Media data download has been aborted but not due to an error.

error

An error is encountered while media data is being downloaded.

emptied

The media buffer has been emptied, possibly due to an error or because the load() method was invoked to reload it.

stalled

Media data is unexpectedly no longer available.

Media Playing Events
--------------------

We also have another set of events that are useful for reacting to the state of the media playback.

### timeupdate

The `timeupdate` event is triggered every time the `currentTime` property changes. In practice, this occurs every 250 milliseconds. This event can be used to trigger the displaying of playback progress.

```js
audio.addEventListener("timeupdate", () => {
  //update something related to playback progress
});
```

### playing

The `playing` event is initiated when playback is ready to start after having being paused due to lack of media data.

### waiting

The `waiting` event is triggered when playback has stopped due to lack of media data, although it is expected to resume once data becomes available.

### play

The `play` event is initiated after the `play()` method is returned or when the `autoplay` attribute has caused playback to begin. This is when the state of the media switches from paused to playing.

### pause

The `pause` event is triggered after the `pause()` method is returned. This is when the states switch from playing to paused.

### ended

The `ended` event is initiated when the end of the media is reached.

```js
audio.addEventListener("ended", () => {
  //do something once audio track has finished playing
});
```

### volumechange

The `volumechange` event signifies that the volume has changed; that includes being muted.

An Audio Player with Feedback
-----------------------------

Consider this snippet of HTML:

```html
<audio id="my-audio">
  <source
 src="http://jPlayer.org/audio/mp3/Miaow-07-Bubble.mp3"
 type="audio/mpeg" />
  <source
 src="http://jPlayer.org/audio/ogg/Miaow-07-Bubble.ogg"
 type="audio/ogg" />
  <!-- place fallback here as <audio> supporting browsers will ignore it -->
  <a href="audiofile.mp3">audiofile.mp3</a>
</audio>

<div id="controls">
  <span id="loading">loading</span>
  <button id="play" style="display:none">play</button>
  <button id="pause" style="display:none">pause</button>
</div>
<div id="progress">
  <div id="bar"></div>
</div>
```

Styled like so:

```css
#controls {
  width: 80px;
  float: left;
}

#progress {
  margin-left: 80px;
  border: 1px solid black;
}

#bar {
  height: 20px;
  background-color: green;
  width: 0;
}
```

Now let's wire this thing up with JavaScript:

```js
window.onload = () => {
  const audio = document.getElementById("my-audio");
  const play = document.getElementById("play");
  const pause = document.getElementById("pause");
  const loading = document.getElementById("loading");
  const bar = document.getElementById("bar");

  function displayControls() {
    loading.style.display = "none";
    play.style.display = "block";
  }

  // Check that the media is ready before displaying the controls
  if (audio.paused) {
    displayControls();
  } else {
    // not ready yet - wait for canplay event
    audio.addEventListener("canplay", () => {
      displayControls();
    });
  }

  play.addEventListener("click", () => {
    audio.play();
    play.style.display = "none";
    pause.style.display = "block";
  });

  pause.addEventListener("click", () => {
    audio.pause();
    pause.style.display = "none";
    play.style.display = "block";
  });

  // Display progress
  audio.addEventListener("timeupdate", () => {
    // Sets the percentage
    bar.style.width = `${Math.floor(
 (audio.currentTime / audio.duration) \* 100,
 )}%`;
  });
};
```

You should end up with something like this:

![A basic audio player with play/pause button and seek bar](/en-US/docs/Web/Media/Audio_and_video_delivery/Cross-browser_audio_basics/simpleplayer.png)

### Seeking using the seek bar

This is a good start, but it would be nice to be able to navigate the audio using the progress bar. Fortunately this isn't too difficult to implement.

First of all, we apply a quick update to the progress bar CSS to display the hand pointer on hover:

```css
#progress {
  margin-left: 80px;
  border: 1px solid black;
  cursor: pointer;
}
```

Then we add the code that detects the click and moves the 'playhead' to the correct position:

```js
const progress = document.getElementById("progress");

progress.addEventListener("click", (e) => {
  // Calculate the normalized position clicked
  const clickPosition = (e.pageX - progress.offsetLeft) / progress.offsetWidth;
  const clickTime = clickPosition \* audio.duration;

  // Move the playhead to the correct position
  audio.currentTime = clickTime;
});
```

### Buffering

Ok, we're getting there, but there is another piece of useful information we can display: the amount of audio has been buffered or downloaded in advance.

There are a couple of properties we haven't looked at yet, `buffered` and `seekable`.

#### buffered

This property lets us know which parts of the audio has been buffered (downloaded in advance). It returns something called a `TimeRanges` object.

```js
bufferedTimeRanges = audio.buffered;
```

#### seekable

The seekable property informs you of whether you can jump directly to that part of the media without further buffering.

```js
seekableTimeRanges = audio.seekable;
```

#### Buffering events

There are also a couple of events related to buffering:

`seeking`

The `seeking` event is fired when media is being sought.

`seeked`

`seeked` occurs when the `seeking` attribute changes to `false`.

**Note:** You can read more on Buffering, Seeking and Time Ranges elsewhere.

Browser support
---------------

The following tables list basic audio support across desktop and mobile browsers, and what audio codecs are supported.

### Desktop

| Desktop Browser | Version |
| --- | --- |
| Chrome | 4+ |
| Firefox | 3.5+ |
| Internet Explorer | 9+ |
| Opera | 10.5+ |
| Safari | 4+ |

### Mobile

| Mobile Browser | Version |
| --- | --- |
| Chrome (Android) | 32+ |
| Firefox (Android) | 26+ |
| IE Mobile | 10+ |
| Opera Mobile | 11+ |
| Safari (iOS) | 4+ |
| Android Browser | 2.3+ |
| Blackberry | 7+ |

Audio Codec Support
-------------------

| Browser | Ogg | MP3 | AAC | PCM | Opus |
| --- | --- | --- | --- | --- | --- |
| Firefox 3.5+ | ✓ | ✓ \*26+ |  | ✓ | ✓ \*14+ |
| Safari 5+ |  | ✓ | ✓ |  |  |
| Chrome 6+ | ✓ | ✓ | ✓ | ✓ \*9+ |  |
| Opera 10.5+ | ✓ |  |  |  |  |
| Internet Explorer 9+ |  | ✓ | ✓ |  |  |
| Firefox Mobile | ✓ | ✓ | ✓ | ✓ | ✓ |
| Safari iOS3+ |  | ✓ | ✓ | ✓ \*4.2+ |  |
| Chrome Mobile | ✓ | ✓ | ✓ | ✓ |  |
| Opera Mobile | ✓ | ✓ | ✓ | ✓ |  |
| Internet Explorer Mobile | ✓ | ✓ | ✓ | ✓ |  |
| Android 2.3+ | ✓ | ✓ | ✓ | ✓ |  |

**Note:** Nearly all browsers support MP3 — for more details see this page on media format browser compatibility.

See also
--------

* Buffering, Seeking and Time Ranges
* HTMLMediaElement events
* Event reference > Media
* Using HTML audio and video
* Creating a cross-browser video player
* jPlayer: An open source audio and video library for jQuery and Zepto.


Media buffering, seeking, and time ranges - Web media technologies

Media buffering, seeking, and time ranges
=========================================

Sometimes it's useful to know how much `<audio>` or `<video>` has downloaded or is playable without delay — a good example of this is the buffered progress bar of an audio or video player. This article discusses how to build a buffer/seek bar using TimeRanges, and other features of the media API.

Buffered
--------

The `buffered` attribute will tell us which parts of the media has been downloaded. It returns a `TimeRanges` object, which will tell us which chunks of media have been downloaded. This is usually contiguous but if the user jumps about while media is buffering, it may contain holes.

This will work with `<audio>` or `<video>`; for now let's consider a simple audio example:

```html
<audio id="my-audio" controls src="music.mp3"></audio>
```

We can access these attributes like so:

```js
const audio = document.getElementById("my-audio");
const bufferedTimeRanges = audio.buffered;
```

TimeRanges Object
-----------------

TimeRanges are a series of non-overlapping ranges of time, with start and stop times. (learn more about TimeRanges).

A `TimeRanges` Object consists of the following properties:

* `length`: The number of time ranges in the object.
* `start(index)`: The start time, in seconds, of a time range.
* `end(index)`: The end time, in seconds, of a time range.

Without any user interaction there is usually only one time range, but if you jump about in the media more than one time range can appear, as illustrated by the below visualization. This represents two buffered time ranges — one spanning 0 to 5 seconds and the second spanning 15 to 19 seconds.
```

------------------------------------------------------
|=============|                    |===========|     |
------------------------------------------------------
0             5                    15          19    21
```

For this audio instance, the associated `TimeRanges` object would have the following available properties:

```js
audio.buffered.length; // returns 2
audio.buffered.start(0); // returns 0
audio.buffered.end(0); // returns 5
audio.buffered.start(1); // returns 15
audio.buffered.end(1); // returns 19
```

To try out and visualize buffered time ranges we can write a bit of HTML:

```html
<p>
  <audio id="my-audio" controls>
    <source src="music.mp3" type="audio/mpeg" />
  </audio>
</p>
<p>
  <canvas id="my-canvas" width="300" height="20"> </canvas>
</p>
```

and a bit of JavaScript:

```js
window.onload = () => {
  const audio = document.getElementById("my-audio");
  const canvas = document.getElementById("my-canvas");
  const context = canvas.getContext("2d");

  context.fillStyle = "lightgray";
  context.fillRect(0, 0, canvas.width, canvas.height);
  context.fillStyle = "red";
  context.strokeStyle = "white";

  const inc = canvas.width / audio.duration;

  // Display TimeRanges

  audio.addEventListener("seeked", () => {
    for (let i = 0; i < audio.buffered.length; i++) {
      const startX = audio.buffered.start(i) \* inc;
      const endX = audio.buffered.end(i) \* inc;
      const width = endX - startX;

      context.fillRect(startX, 0, width, canvas.height);
      context.rect(startX, 0, width, canvas.height);
      context.stroke();
    }
  });
};
```

This works better with longer pieces of audio or video, but press play and click about the player progress bar and you should get something like this. Each red filled white rectangle represents a time range.

![A simple audio player with play button, seek bar and volume control, with a series of red rectangles beneath it representing time ranges.](/en-US/docs/Web/Media/Audio_and_video_delivery/buffering_seeking_time_ranges/bufferedtimeranges.png)

**Note:** You can see the timerange code running live on JS Bin.

Seekable
--------

 The `seekable` attribute returns a `TimeRanges` object and tells us which parts of the media can be played without delay; this is irrespective of whether that part has been downloaded or not. Some parts of the media may be seekable but not buffered if byte-range requests are enabled on the server. Byte range requests allow parts of the media file to be delivered from the server and so can be ready to play almost immediately — thus they are seekable.
 For more information on byte range requests see HTTP range requests.

```js
const seekableTimeRanges = audio.seekable;
```

Creating our own Buffering Feedback
-----------------------------------

If we wish to create our own custom player, we may want to provide feedback on how much of the media is ready to be played. In practice a good way to do this is use the `seekable` attribute, although as we have seen above seekable parts of the media are not necessarily contiguous — they often are however and we can safely approximate this information to give the user an indication of which parts of the media can be played directly. We can find this point in the media using the following line of code:

```js
const seekableEnd = audio.seekable.end(audio.seekable.length - 1);
```

**Note:** `audio.seekable.end(audio.seekable.length - 1)` actually tells us the end point of the last time range that is seekable (not all seekable media). In practice this is good enough as the browser either enables range requests or it doesn't. If it doesn't then `audio.seekable` will be equivalent to `audio.buffered`, which will give a valid indication of the end of seekable media. If range requests are enabled this value usually becomes the duration of the media almost instantly.

It is better perhaps to give an indication of how much media has actually downloaded — this what the browser's native players seem to display.

So let's build this. The HTML for our player looks like this:

```html
<audio id="my-audio" preload controls>
  <source src="music.mp3" type="audio/mpeg" />
</audio>
<div class="buffered">
  <span id="buffered-amount"></span>
</div>
<div class="progress">
  <span id="progress-amount"></span>
</div>
```

We'll use the following CSS to style the buffering display:

```css
.buffered {
  height: 20px;
  position: relative;
  background: #555;
  width: 300px;
}

#buffered-amount {
  display: block;
  height: 100%;
  background-color: #777;
  width: 0;
}

.progress {
  margin-top: -20px;
  height: 20px;
  position: relative;
  width: 300px;
}

#progress-amount {
  display: block;
  height: 100%;
  background-color: #595;
  width: 0;
}
```

And the following JavaScript provides our functionality:

```js
window.onload = () => {
  const audio = document.getElementById("my-audio");

  audio.addEventListener("progress", () => {
    const duration = audio.duration;
    if (duration > 0) {
      for (let i = 0; i < audio.buffered.length; i++) {
        if (
          audio.buffered.start(audio.buffered.length - 1 - i) <
          audio.currentTime
        ) {
          document.getElementById("buffered-amount").style.width = `${
 (audio.buffered.end(audio.buffered.length - 1 - i) \* 100) / duration
 }%`;
          break;
        }
      }
    }
  });

  audio.addEventListener("timeupdate", () => {
    const duration = audio.duration;
    if (duration > 0) {
      document.getElementById("progress-amount").style.width = `${
 (audio.currentTime / duration) \* 100
 }%`;
    }
  });
};
```

The progress event is fired as data is downloaded, this is a good event to react to if we want to display download or buffering progress.

The timeupdate event is fired 4 times a second as the media plays and that's where we increment our playing progress bar.

This should give you results similar to the following, where the light grey bar represents the buffered progress and green bar shows the played progress:

![A simple audio player with play button, seek bar and volume control, with a bar below it. The bar has a red portion to show played video, and a dark gray bar to show how much has been buffered.](/en-US/docs/Web/Media/Audio_and_video_delivery/buffering_seeking_time_ranges/bufferedprogress.png)

**Note:** You can see the buffering code running live on JS Bin.

A quick word about Played
-------------------------

It's worth mentioning the `played` property — this tells us which time ranges have been played within the media. For example:

```js
const played = audio.played; // returns a TimeRanges object
```

This could be useful for establishing the parts of your media that are most listened to or watched.


Adding captions and subtitles to HTML video - Web media technologies

Adding captions and subtitles to HTML video
===========================================

In other articles we looked at how to build a cross browser video player using the `HTMLMediaElement` and `Window.fullScreen` APIs, and also at how to style the player. This article will take the same player and show how to add captions and subtitles to it, using `the WebVTT format` and the `<track>` element.

Captioned video example
-----------------------

In this article, we will refer to the Video player with captions example. This example uses an excerpt from the Sintel open movie, created by the Blender Foundation.

![Video player with stand controls such as play, stop, volume, and captions on and off. The video playing shows a scene of a man holding a spear-like weapon, and a caption reads "Esta hoja tiene pasado oscuro."](/en-US/docs/Web/Media/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video/video-player-with-captions.png)

**Note:** You can find the source on GitHub, and also view the example live.

HTML and Video Captions
-----------------------

Before diving into how to add captions to the video player, there are a number of things that we will first mention, which you should be aware of before we start.

### Captions versus subtitles

Captions and subtitles are not the same thing: they have significantly different audiences, and convey different information, and it is recommended that you read up on the differences if you are not sure what they are. They are however implemented in the same way technically, so the material in this article will apply to both.

For this article we will refer to the text tracks displayed as subtitles, as their content is aimed at hearing people who have difficulty understanding the language of the film, rather than deaf or hard-of-hearing people.

### The <track> element

HTML allows us to specify subtitles for a video using the `<track>` element. The various attributes of this element allow us to specify such things as the type of content that we're adding, the language it's in, and of course a reference to the text file that contains the actual subtitle information.

### WebVTT

The files that contain the actual subtitle data are simple text files that follow a specified format, in this case the Web Video Text Tracks (WebVTT) format. The WebVTT specification is still being worked on, but major parts of it are stable so we can use it today.

Video providers (such as the Blender Foundation) provide captions and subtitles in a text format with their videos, but they're usually in the SubRip Text (SRT) format. These can be easily converted to WebVTT using an online converter.

Modifications to the HTML and CSS
---------------------------------

This section summarizes the modifications made to the previous article's code in order to facilitate the addition of subtitles to the video. If you are not interested in this, and just want to get straight into the JavaScript and more relevant CSS, skip to the Subtitle implementation section.

In this example we are using a different video, Sintel, as it actually has some speech in it and therefore is better for illustrating how subtitles work!

### HTML Markup

As mentioned above, we need to make use of the new HTML `<track>` element to add our subtitle files to the HTML video. We actually have our subtitles in three different languages — English, German, and Spanish — so we will reference all three of the relevant VTT files by adding `<track>` elements inside our HTML `<video>` element:

```html
<video id="video" controls preload="metadata">
  <source src="video/sintel-short.mp4" type="video/mp4" />
  <source src="video/sintel-short.webm" type="video/webm" />
  <track
 label="English"
 kind="subtitles"
 srclang="en"
 src="captions/vtt/sintel-en.vtt"
 default />
  <track
 label="Deutsch"
 kind="subtitles"
 srclang="de"
 src="captions/vtt/sintel-de.vtt" />
  <track
 label="Español"
 kind="subtitles"
 srclang="es"
 src="captions/vtt/sintel-es.vtt" />
</video>
```

As you can see, each `<track>` element has the following attributes set:

* `kind` is given a value of `subtitles`, indicating the type of content the files contain
* `label` is given a value indicating which language that subtitle set is for — for example `English` or `Deutsch` — these labels will appear in the user interface to allow the user to easily select which subtitle language they want to see.
* `src` is assigned a valid URL pointing to the relevant WebVTT subtitle file in each case.
* `srclang` indicates what language each subtitle files' contents are in.
* The `default` attribute is set on the English `<track>` element, indicating to the browser that this is the default subtitle file definition to use when subtitles have been turned on and the user has not made a specific selection.

In addition to adding the `<track>` elements, we have also added a new button to control the subtitles menu that we will build. As a consequence, the video controls now look as follows:

```html
<div id="video-controls" class="controls" data-state="hidden">
  <button id="playpause" type="button" data-state="play">Play/Pause</button>
  <button id="stop" type="button" data-state="stop">Stop</button>
  <div class="progress">
    <progress id="progress" value="0" min="0">
      <span id="progress-bar"></span>
    </progress>
  </div>
  <button id="mute" type="button" data-state="mute">Mute/Unmute</button>
  <button id="volinc" type="button" data-state="volup">Vol+</button>
  <button id="voldec" type="button" data-state="voldown">Vol-</button>
  <button id="fs" type="button" data-state="go-fullscreen">Fullscreen</button>
  <button id="subtitles" type="button" data-state="subtitles">CC</button>
</div>
```

### CSS Changes

The video controls have undergone some minor changes in order to make space for the extra button, but these are relatively straightforward.

No image is used for the captions button, so it is styled as:

```css
.controls button[data-state="subtitles"] {
  height: 85%;
  text-indent: 0;
  font-size: 16px;
  font-size: 1rem;
  font-weight: bold;
  color: #666;
  background: #000;
  border-radius: 2px;
}
```

There are also other CSS changes that are specific to some extra JavaScript implementation, but these will be mentioned at the appropriate place below.

Subtitle implementation
-----------------------

A lot of what we do to access the video subtitles revolves around JavaScript. Similar to the video controls, if a browser supports HTML video subtitles, there will be a button provided within the native control set to access them. However, since we have defined our own video controls, this button is hidden, and we need to define our own.

Browsers do vary as to what they support, so we will be attempting to bring a more unified UI to each browser where possible. There's more on browser compatibility issues later on.

### Initial setup

As with all the other buttons, one of the first things we need to do is store a handle to the subtitles' button:

```js
const subtitles = document.getElementById("subtitles");
```

We also initially turn off all subtitles, in case the browser turns any of them on by default:

```js
for (let i = 0; i < video.textTracks.length; i++) {
  video.textTracks[i].mode = "hidden";
}
```

The `video.textTracks` property contains an array of all the text tracks attached to the video. We loop through each one and set its `mode` to `hidden`.

Note: The WebVTT API gives us access to all the text tracks that are defined for an HTML video using the `<track>` element.

### Building a caption menu

Our aim is to use the `subtitles` button we added earlier to display a menu that allows users to choose which language they want the subtitles displayed in, or to turn them off entirely.

We have added the button, but before we make it do anything, we need to build the menu that goes with it. This menu is built dynamically, so that languages can be added or removed later by editing the `<track>` elements within the video's markup.

All we need to do is to go through the video's `textTracks`, reading their properties and building the menu up from there:

```js
let subtitlesMenu;
if (video.textTracks) {
  const df = document.createDocumentFragment();
  const subtitlesMenu = df.appendChild(document.createElement("ul"));
  subtitlesMenu.className = "subtitles-menu";
  subtitlesMenu.appendChild(createMenuItem("subtitles-off", "", "Off"));
  for (let i = 0; i < video.textTracks.length; i++) {
    subtitlesMenu.appendChild(
      createMenuItem(
        `subtitles-${video.textTracks[i].language}`,
        video.textTracks[i].language,
        video.textTracks[i].label,
      ),
    );
  }
  videoContainer.appendChild(subtitlesMenu);
}
```

This code creates a `documentFragment`, which is used to hold an unordered list containing our subtitles menu. First of all an option is added to allow the user to switch all subtitles off, and then buttons are added for each text track, reading the language and label from each one.

The creation of each list item and button is done by the `createMenuItem()` function, which is defined as follows:

```js
const subtitleMenuButtons = [];
function createMenuItem(id, lang, label) {
  const listItem = document.createElement("li");
  const button = listItem.appendChild(document.createElement("button"));
  button.setAttribute("id", id);
  button.className = "subtitles-button";
  if (lang.length > 0) button.setAttribute("lang", lang);
  button.value = label;
  button.setAttribute("data-state", "inactive");
  button.appendChild(document.createTextNode(label));
  button.addEventListener("click", (e) => {
    // Set all buttons to inactive
    subtitleMenuButtons.forEach((button) => {
      button.setAttribute("data-state", "inactive");
    });

    // Find the language to activate
    const lang = button.getAttribute("lang");
    for (let i = 0; i < video.textTracks.length; i++) {
      // For the 'subtitles-off' button, the first condition will never match so all will subtitles be turned off
      if (video.textTracks[i].language === lang) {
        video.textTracks[i].mode = "showing";
        button.setAttribute("data-state", "active");
      } else {
        video.textTracks[i].mode = "hidden";
      }
    }
    subtitlesMenu.style.display = "none";
  });
  subtitleMenuButtons.push(button);
  return listItem;
}
```

This function builds the required `<li>` and `<button>` elements, and returns them so they can be added to the subtitles menu list. It also sets up the required event listeners on the button to toggle the relevant subtitle set on or off. This is done by setting the required subtitle's `mode` attribute to `showing`, and setting the others to `hidden`.

Once the menu is built, it is then inserted into the DOM at the bottom of the videoContainer.

Initially the menu is hidden by default, so an event listener needs to be added to our subtitles button to toggle it:

```js
subtitles.addEventListener("click", (e) => {
  if (subtitlesMenu) {
    subtitlesMenu.style.display =
      subtitlesMenu.style.display === "block" ? "none" : "block";
  }
});
```

### Subtitle menu CSS

We also added some rudimentary styling for the newly created subtitles menu:

```css
.subtitles-menu {
  display: none;
  position: absolute;
  bottom: 14.8%;
  right: 20px;
  background: #666;
  list-style-type: none;
  margin: 0;
  width: 100px;
  padding: 10px;
}

.subtitles-menu li {
  padding: 0;
  text-align: center;
}

.subtitles-menu li button {
  border: none;
  background: #000;
  color: #fff;
  cursor: pointer;
  width: 90%;
  padding: 2px 5px;
  border-radius: 2px;
}
```

Styling the displayed subtitles
-------------------------------

One of the less well known about and supported features of WebVTT is the ability to style the individual subtitles (something called text cues) via CSS Extensions.

The `::cue` pseudo-element is the key to targeting individual text track cues for styling, as it matches any defined cue. There are only a handful of CSS properties that can be applied to a text cue:

* `color`
* `opacity`
* `visibility`
* `text-decoration`
* `text-shadow`
* `background` shorthand properties
* `outline` shorthand properties
* `font` shorthand properties, including `line-height`
* `white-space`

For example, to change the text color of the text track cues you can write:

```css
::cue {
  color: #ccc;
}
```

If the WebVTT file uses voice spans, which allow cues to be defined as having a particular "voice":
```

0
00:00:00.000 --> 00:00:12.000
<v Test>[Test]</v>
```

Then this specific 'voice' will be stylable like so:

```css
::cue(v[voice="Test"]) {
  color: #fff;
  background: #0095dd;
}
```

**Note:** Some of the styling of cues with ::cue currently works on Chrome, Opera, and Safari, but not yet on Firefox.

Browser compatibility
---------------------

Browser support for WebVTT and the `<track>` element is fairly good, although some browsers differ slightly in their implementation.

### Safari

In Safari 6.1+, subtitles are enabled by default, and the default controls contain a button and a menu that offers the same functionality as the menu we just built, along with an "Auto" option which allows the browser to choose. The `default` attribute is also supported.

### Chrome and Opera

These browsers have similar implementations again: subtitles are enabled by default and the default control set contains a 'cc' button that turns subtitles on and off. Chrome and Opera ignore the `default` attribute on the `<track>` element and will instead try to match the browser's language to the subtitle's language.

Plugins
-------

 There are also many open-source and commercial HTML video-player plugins that offer caption and subtitle support that you can use instead of rolling your own.
 You can search for those on the web using search terms like *"HTML video player plugin"*.


Video player styling basics - Web media technologies

Video player styling basics
===========================

In the previous Cross browser video player article we described how to build a cross-browser HTML video player using the Media and Fullscreen APIs. This follow-up article looks at how to style this custom player, including making it responsive.

The example in action
---------------------

![A video player with play, stop, volume and fullscreen controls, showing an image of a soldier.](/en-US/docs/Web/Media/Audio_and_video_delivery/Video_player_styling_basics/video-player-styled.png)

You can find the code for the updated, styled example on GitHub, and view it live.

Preliminary modifications from the original example
---------------------------------------------------

This section summarizes the modifications that were made to the original video player example to make the styling task easier, before the bulk of the work was started.

### HTML Markup

There are a few changes that were made to the HTML markup shown in the previous article. The custom video controls and `<progress>` element are now contained within `<div>` elements, rather than residing inside unordered list items.

The markup for the custom controls now looks as follows:

```html
<div id="video-controls" class="controls" data-state="hidden">
  <button id="playpause" type="button" data-state="play">Play/Pause</button>
  <button id="stop" type="button" data-state="stop">Stop</button>
  <div class="progress">
    <progress id="progress" value="0" min="0">
      <span id="progress-bar"></span>
    </progress>
  </div>
  <button id="mute" type="button" data-state="mute">Mute/Unmute</button>
  <button id="volinc" type="button" data-state="volup">Vol+</button>
  <button id="voldec" type="button" data-state="voldown">Vol-</button>
  <button id="fs" type="button" data-state="go-fullscreen">Fullscreen</button>
</div>
```

### Related CSS alteration

The previous article set the `display` property of the video controls to `block` in order to display them. This has now been changed to use a `data-state` attribute, which this code already uses to handle its fullscreen implementation.

This "data-state" idea is also used for setting the current state of buttons within the video control set, which allows specific state styling.

### JavaScript

As mentioned above, a `data-state` attribute is used in various places for styling purposes and these are set using JavaScript. Specific implementations will be mentioned at appropriate places below.

Styling
-------

The resultant video player style used here is rather basic — this is intentional, as the purpose is to show how such a video player could be styled and be made responsive.

**Note:** in some cases some basic CSS is omitted from the code examples here as its use is either obvious or not specifically relevant to styling the video player.

### Basic styling

The HTML video and its controls are all contained within a `<figure>` element, which is given a maximum width and height (based on the dimensions of the video used) and centered within the page:

```css
figure {
  max-width: 64rem;
  width: 100%;
  max-height: 30.875rem;
  height: 100%;
  margin: 1.25rem auto;
  padding: 1.051%;
  background-color: #666;
}
```

The video controls container itself also needs some styling so that it is set up the correct way:

```css
.controls {
  width: 100%;
  height: 8.0971659919028340080971659919028%; /\* of figure's height \*/
  position: relative;
}
```

The height of the `.controls` class is set to be (a very precise!) percentage of the enclosing `<figure>` element (this was worked out with experimentation based on the required button height). Its position is also specifically set to `relative`, which is required for its responsiveness (more on that later).

As mentioned earlier, a `data-state` attribute is now used to indicate whether the video controls are visible or not and these also need to be styled:

```css
.controls[data-state="hidden"] {
  display: none;
}

.controls[data-state="visible"] {
  display: block;
}
```

There are a number of properties that also need to be set for all elements within the video controls:

```css
.controls > \* {
  float: left;
  width: 3.90625%;
  height: 100%;
  margin-left: 0.1953125%;
  display: block;
}

.controls > \*:first-child {
  margin-left: 0;
}
```

All elements are floated left, as they are to be aligned next to one another, and each element is set to have a `width` of nearly 4% (again the actual value was calculated based on the required dimensions of the buttons), and a `height` of 100%. A value for `margin-left` is also set, but the first element (in this case the play/pause button) has this property overridden by the value 0.

The `<div>` container for the `<progress>` element also requires some specific settings; it is set to be much wider than the other child elements and its cursor value is set to be pointer:

```css
.controls .progress {
  cursor: pointer;
  width: 75.390625%;
}
```

### Buttons

The first major styling task to tackle is to make the video control's buttons actually look like and act like real buttons.

Each button has some basic styling:

```css
.controls button {
  border: none;
  cursor: pointer;
  background: transparent;
  background-size: contain;
  background-repeat: no-repeat;
}
```

By default, all `<button>` elements have a border, so this is removed. Since background images will be used to display appropriate icons, the background color of the button is set to be transparent, non-repeated, and the element should fully contain the image.

Simple `:hover` and `:focus` states are then set for each button that alters the opacity of the button:

```css
.controls button:hover,
.controls button:focus {
  opacity: 0.5;
}
```

To obtain appropriate button images, a set of free common control set icons was downloaded from the web. Each image was then converted to a base64 encoded string (using an online base64 image encoder), since the images are quite small, the resultant encoded strings are quite short.

Since some buttons have dual functionality, e.g. play/pause, and mute/unmute, these buttons have different states that need to be styled. As mentioned earlier, a `data-state` variable is used to indicate which state such buttons are currently in.

For example, the play/pause button has the following background image definitions (the full base64 strings have been omitted for brevity):

```css
.controls button[data-state="play"] {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAA…");
}

.controls button[data-state="pause"] {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAA…");
}
```

When the `data-state` of the button is changed, the appropriate image will also be changed. All the other buttons are treated in a similar way.

### Progress bar

The `<progress>` element has the following basic style set up:

```css
.controls progress {
  display: block;
  width: 100%;
  height: 81%;
  margin-top: 0.125rem;
  border: none;
  color: #0095dd;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
}
```

Like the `<button>` elements, `<progress>` also has a default border, which is removed here. It is also given a slight rounded corner for aesthetic reasons.

As mentioned in the previous article, there is a fallback provided for browsers that do not support the `<progress>` element; this also needs to be styled appropriately:

```css
.controls progress[data-state="fake"] {
  background: #e6e6e6;
  height: 65%;
}
.controls progress span {
  width: 0%;
  height: 100%;
  display: inline-block;
  background-color: #2a84cd;
}
```

A `.data-state` class is also used here when a `<progress>` element is being "faked"; when it's in this state the background color needs to be set. The internal `<span>` element used as the actual progressing part of the faked progress bar has its width initially set to 0% (it is updated via JavaScript) and it also has its background color set.

There are some browser-specific properties that need to be set to ensure that Firefox and Chrome use the required color for the progress bar:

```css
.controls progress::-moz-progress-bar {
  background-color: #0095dd;
}

.controls progress::-webkit-progress-value {
  background-color: #0095dd;
}
```

Although the same properties are set to the same value, these rules need to be defined separately, otherwise Chrome ignores it.

JavaScript
----------

That's really it for the immediate styling; the next task is making a number of JavaScript changes to ensure that everything works as expected.

### Control visibility

The first change is simple: the `data-state` for showing the video controls when JavaScript is available to the browser now needs to be set:

```js
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");
```

### Progress bar support

A check also needs to be made to set up the "fake" progress bar if the browser doesn't support the `<progress>` element:

```js
const supportsProgress = document.createElement("progress").max !== undefined;
if (!supportsProgress) progress.setAttribute("data-state", "fake");
```

### Button functionality

This section looks at the JavaScript required for implementing the button functionality.

#### Play/Pause and Mute

Now that the buttons actually look like buttons and have images that indicate what they do, some changes need to be made so that the "dual functionality" buttons (such as the play/pause button) are in the correct "state" and display the correct image. In order to facilitate this, a new function is defined called `changeButtonState()`, which accepts a type variable indicating the button's functionality:

```js
function changeButtonState(type) {
  if (type === "playpause") {
    // Play/Pause button
    if (video.paused || video.ended) {
      playpause.setAttribute("data-state", "play");
    } else {
      playpause.setAttribute("data-state", "pause");
    }
  } else if (type === "mute") {
    // Mute button
    mute.setAttribute("data-state", video.muted ? "unmute" : "mute");
  }
}
```

This function is then called by the relevant event handlers:

```js
video.addEventListener(
  "play",
  () => {
    changeButtonState("playpause");
  },
  false,
);

video.addEventListener(
  "pause",
  () => {
    changeButtonState("playpause");
  },
  false,
);

stop.addEventListener("click", (e) => {
  video.pause();
  video.currentTime = 0;
  progress.value = 0;

  // Update the play/pause button's 'data-state' which allows the correct button image to be set via CSS
  changeButtonState("playpause");
});

mute.addEventListener("click", (e) => {
  video.muted = !video.muted;
  changeButtonState("mute");
});
```

You might have noticed that there are new handlers where the `play` and `pause` events are reacted to on the video. There is a reason for this! Even though the browser's default video control set has been turned off, many browsers make them accessible by right-clicking on the HTML video. This means that a user could play/pause the video from these controls, which would then leave the custom control set's buttons out of sync. If a user uses the default controls, the defined Media API events — such as `play` and `pause` — are raised so this can be taken advantage of to ensure that the custom control buttons are kept in sync. To ensure this, a new click handler needs to be defined for the play/pause button so that it too raises the `play` and `pause` events:

```js
playpause.addEventListener("click", (e) => {
  if (video.paused || video.ended) {
    video.play();
  } else {
    video.pause();
  }
});
```

#### Volume

The `alterVolume()` function, called when the player's volume buttons are clicked, also changes — it now calls a new function called `checkVolume()`:

```js
function checkVolume(dir) {
  if (dir) {
    const currentVolume = Math.floor(video.volume \* 10) / 10;
    if (dir === "+" && currentVolume < 1) {
      video.volume += 0.1;
    } else if (dir === "-" && currentVolume > 0) {
      video.volume -= 0.1;
    }

    // If the volume has been turned off, also set it as muted
    // Note: can only do this with the custom control set as when the 'volumechange' event is raised,
    // there is no way to know if it was via a volume or a mute change
    video.muted = currentVolume <= 0;
  }
  changeButtonState("mute");
}

const alterVolume = (dir) => {
  checkVolume(dir);
};
```

This new `checkVolume()` function does the same thing as the `alterVolume()` but it also sets the state of the mute button depending on the video's current volume setting. `checkVolume()` is also called when the `volumechange` event is raised:

```js
video.addEventListener(
  "volumechange",
  () => {
    checkVolume();
  },
  false,
);
```

#### Progress bar

A small change also needs to be made to the click handler for the `<progress>` element. Since the enclosing `<figure>` element now has `position:relative` set on it, the calculations made by this click handler are incorrect. It now also needs to take into account the offset position of the parent element:

```js
progress.addEventListener("click", (e) => {
  const pos =
    (e.pageX - progress.offsetLeft - progress.offsetParent.offsetLeft) /
    progress.offsetWidth;
  video.currentTime = pos \* video.duration;
});
```

#### Fullscreen

The FullScreen implementation hasn't changed.

Responsive styling
------------------

Now that the player has its basic look and feel taken care of, some other styling changes — involving media queries — need to be made in order to make it responsive.

The player currently works fairly well until displayed on a "medium" screen (e.g. 1024px/64em) or smaller. In this case, the margins and padding on the `<figure>` element need to be removed so that all the available space is taken advantage of, and the buttons are a bit too small so this needs to be altered by setting a new height on the element that has the `.controls` class set on it:

```css
@media screen and (max-width: 64em) {
  figure {
    padding-left: 0;
    padding-right: 0;
    height: auto;
  }

  .controls {
    height: 1.876rem;
  }
}
```

This works well enough until it is viewed on a smaller screen (680px/42.5em), so another breakpoint is made here. Since the height of the `.controls` class element will now vary, a fixed height is no longer required — it is therefore set to `auto`. The definitions for the elements within the `.controls` element now also need to be changed:

```css
@media screen and (max-width: 42.5em) {
  .controls {
    height: auto;
  }

  .controls > \* {
    display: block;
    width: 16.6667%;
    margin-left: 0;
    height: 2.5rem;
    margin-top: 2.5rem;
  }

  .controls .progress {
    position: absolute;
    top: 0;
    width: 100%;
    float: none;
    margin-top: 0;
  }

  .controls .progress progress {
    width: 98%;
    margin: 0 auto;
  }

  .controls button {
    background-position: center center;
  }
}
```

The `.progress` container is now moved to the top of the control set via `position:absolute`, so it and all the buttons need to be wider. In addition, the buttons need to be pushed below the progress container so that they are visible.


Web Audio playbackRate explained - Web media technologies

Web Audio playbackRate explained
================================

The `playbackRate` property of the `<audio>` and `<video>` elements allows us to change the speed, or rate, at which a piece of web audio or video is playing. This article explains `playbackRate` in detail.

playbackRate basics
-------------------

Let's starting by looking at a brief example of `playbackRate` usage:

```js
const audio = document.createElement("audio");
audio.setAttribute("src", "audiofile.mp3");
audio.playbackRate = 0.5;
```

Here we create an `<audio>` element, and set its `src` to a file of our choice. Next we set `playbackRate` to 0.5, which represents half normal speed (the `playbackRate` is a multiplier applied to the original rate).

A complete example
------------------

Let's create a `<video>` element first, and set up video and playback rate controls in HTML:

```html
<video id="myVideo" controls>
  <source
 src="http://jplayer.org/video/m4v/Big\_Buck\_Bunny\_Trailer.m4v"
 type="video/mp4" />
  <source
 src="http://jplayer.org/video/webm/Big\_Buck\_Bunny\_Trailer.webm"
 type="video/webm" />
</video>

<form>
  <input id="pbr" type="range" value="1" min="0.5" max="4" step="0.1" />
  <p>Playback Rate <span id="currentPbr">1</span></p>
</form>
```

And apply some JavaScript to it:

```js
window.onload = () => {
  const v = document.getElementById("myVideo");
  const p = document.getElementById("pbr");
  const c = document.getElementById("currentPbr");

  p.addEventListener(
    "input",
    () => {
      c.innerHTML = p.value;
      v.playbackRate = p.value;
    },
    false,
  );
};
```

Finally, we listen for the `input` event firing on the `<input>` element, allowing us to react to the playback rate control being changed.

**Note:** Try out this example live, and try adjusting the playback rate control to see the effect.

defaultPlaybackRate and ratechange
----------------------------------

In addition to `playbackRate`, we also have a `defaultPlaybackRate` property available, which lets us set the default playback rate: the playback rate to which the media resets; for example, if we change the source of the video, or (in some browsers) when an `ended` event is generated.

So `defaultPlaybackRate` allows us to set the playback rate *before* playing the media, while `playbackRate` allows us to change it during media playback.

There is also an event available called `ratechange`, which fires every time the `playbackRate` changes.

Browser support
---------------

* Chrome 20+ ✔
* Firefox 20+ ✔
* IE 9+ ✔
* Safari 6+ ✔
* Opera 15+ ✔
* Mobile Chrome (Android) ✖
* Mobile Firefox 24+ ✔
* IE Mobile ✖
* Mobile Safari 6+ (iOS) ✔
* Opera Mobile ✖
### Notes

* Most browsers stop playing audio outside `playbackRate` bounds of 0.5 and 4, leaving the video playing silently. For most applications, it's recommended that you limit the range to between 0.5 and 4.
* The pitch of the audio track does not change when `playBackRate` is altered.
* Negative values will not cause the media to play in reverse.
* IE9+ switches to the default playback rate when an `ended` event is fired.
* Firefox generates a `ratechange` event when the media source is substituted.
* On iOS 7 you can only affect the `playbackRate` when the media is paused (not while it's playing).
See also
--------

* Hyperaudio's Playback Rate Test
* HTMLMediaElement reference


Setting up adaptive streaming media sources - Web media technologies

Setting up adaptive streaming media sources
===========================================

Let's say you want to set up an adaptive streaming media source on a server, to be consumed inside an HTML media element. How would you do that? This article explains how, looking at two of the most common formats: MPEG-DASH and HLS (HTTP Live Streaming.)

Choosing formats
----------------

In terms of adaptive streaming formats, there are many to choose from; we decided to choose the following two as between them we can support most modern browsers.

* MPEG-DASH
* HLS (HTTP Live Streaming)

In order to adaptively stream media we need to split the media up into chunks. We are required to provide several different quality files split up over several time points. The more qualities and time points there are, the more 'adaptive' your stream will be, but we will usually want to find a pragmatic balance between size, time to encode and adaptiveness.

The good news is that once we have encoded our media in the appropriate format we are pretty good to go. For adaptive streaming over HTTP, no special server-side components are required.

Both MPEG-DASH and HLS use a playlist format to structure the component piece of media that make the possible streams. Various bitrate streams are broken into segments and placed in appropriate server folders — we have to provide our media players with a link to lookup files or playlists specifying the name and location of these stream folders.

MPEG-DASH Encoding
------------------

MPEG-DASH is an adaptive bitrate streaming technique that enables streaming of media content over the Internet delivered from conventional HTTP web servers.

A media presentation description (MPD) file is used to hold the information on the various streams and the bandwidths they are associated with. In your video source (src) attribute you point to the MPD instead of to the media file as you would with non-adaptive media.

The MPD file tells the browser where the various pieces of media are located, it also includes metadata such as mimeType and codecs and there are other details such as byte-ranges in there too - it is an XML document and in many cases will be generated for you.

There are a few profiles we can use. We're going to take a look at Ondemand profile for Video On Demand (VOD) and the LIVE profile.

For live services streaming, the LIVE profile is a requirement. The stream switching capabilities are identical between the profiles.

Other reasons to use LIVE profile over Ondemand for VOD content may be:

1. Your client or server does not support range requests
2. Your server cannot cache range requests efficiently
3. Your server cannot prefetch range requests efficiently
4. The SIDX\* is large and having to load it first slows down startup a little
5. You want to use the original files for both DASH and other forms of delivery (such as Microsoft Smooth Streaming) as a transition strategy
6. You can use the same media files for both live transmission and VOD at a later stage

\*SIDX or SegmentIndexBox is a structure describing a segment by giving its earliest presentation time and other meta-data and can often make up a large portion of the MPD file.

### Ondemand Profile

This profile will allow switching between streams 'on demand' - that is to say that you only need provide a set of contiguous files and specify the bandwidth for each one and the appropriate file will be chosen automatically.

Here's a simple example that provides an audio track representation and four separate video representations.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="urn:mpeg:dash:schema:mpd:2011"
 xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"
 type="static"
 mediaPresentationDuration="PT654S"
 minBufferTime="PT2S"
 profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">

  <BaseURL>http://example.com/ondemand/</BaseURL>
  <Period>
    <!-- English Audio -->
    <AdaptationSet mimeType="audio/mp4" codecs="mp4a.40.5" lang="en" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
      <Representation id="1" bandwidth="64000">
        <BaseURL>ElephantsDream_AAC48K_064.mp4.dash</BaseURL>
      </Representation>
    </AdaptationSet>
    <!-- Video -->
    <AdaptationSet mimeType="video/mp4" codecs="avc1.42401E" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
      <Representation id="2" bandwidth="100000" width="480" height="360">
        <BaseURL>ElephantsDream_H264BPL30_0100.264.dash</BaseURL>
      </Representation>
      <Representation id="3" bandwidth="175000" width="480" height="360">
        <BaseURL>ElephantsDream_H264BPL30_0175.264.dash</BaseURL>
      </Representation>
      <Representation id="4" bandwidth="250000" width="480" height="360">
        <BaseURL>ElephantsDream_H264BPL30_0250.264.dash</BaseURL>
      </Representation>
      <Representation id="5" bandwidth="500000" width="480" height="360">
        <BaseURL>ElephantsDream_H264BPL30_0500.264.dash</BaseURL>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>
```

Once you have generated your MPD file you can reference it from within the video tag.

```html
<video src="my.mpd" type="application/dash+xml"></video>
```

it might be wise to provide a fallback for browsers that don't yet support MPEG-DASH:

```html
<video>
  <source src="my.mpd" type="application/dash+xml" />
  <!-- fallback -->
  <source src="my.mp4" type="video/mp4" />
  <source src="my.webm" type="video/webm" />
</video>
```

### LIVE Profile

A useful piece of software when dealing with MPEG-DASH is Dash Encoder. This uses MP4Box to encode media into MPEG-DASH format.

**Note:** You will need to be comfortable with make files and installing dependencies to get this software up and running.

**Note:** Since MPEG-DASH decoding is done partially using JavaScript and MSE files are often grabbed using XHR, keep same origin rules in mind.

**Note:** If you use WebM you can use the methods shown in this tutorial DASH Adaptive Streaming for HTML 5 Video.

Once encoded your file structure may look something like this:
```

play list ->                /segments/news.mp4.mpd

main segment folder ->      /segments/main/

100 Kbps segment folder ->  /segments/main/news100 contains (1.m4s, 2.m4s, 3.m4s … )

200 Kbps segment folder ->  /segments/main/news200 contains (1.m4s, 2.m4s, 3.m4s … )

300 Kbps segment folder ->  /segments/main/news300 contains (1.m4s, 2.m4s, 3.m4s … )

400 Kbps segment folder ->  /segments/main/news400 contains (1.m4s, 2.m4s, 3.m4s … )
```

The playlist or `.mpd` file contains XML that explicitly lists where all the various bitrate files reside.

```xml
<?xml version="1.0"?>
  <MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="static" mediaPresentationDuration="PT0H9M56.46S">
    <BaseURL>
      http://example.com/segments
    </BaseURL>
    <Period start="PT0S">
      <AdaptationSet bitstreamSwitching="true">

        <Representation id="0" codecs="avc1" mimeType="video/mp4" width="320" height="240" startWithSAP="1" bandwidth="46986">
          <SegmentBase>
            <Initialization sourceURL="main/news100/1.m4s" range="0-862"/>
          </SegmentBase>
          <SegmentList duration="1">
            <SegmentURL media="main/news100/2.m4s" mediaRange="863-7113"/>
            <SegmentURL media="main/news100/3.m4s" mediaRange="7114-14104"/>
            <SegmentURL media="main/news100/4.m4s" mediaRange="14105-17990"/>
          </SegmentList>
        </Representation>

        <Representation id="1" codecs="avc1" mimeType="video/mp4" width="320" height="240" startWithSAP="1" bandwidth="91932">
          <SegmentBase>
            <Initialization sourceURL="main/news200/1.m4s" range="0-864"/>
          </SegmentBase>
          <SegmentList duration="1">
            <SegmentURL media="main/news200/2.m4s" mediaRange="865-11523"/>
            <SegmentURL media="main/news200/3.m4s" mediaRange="11524-25621"/>
            <SegmentURL media="main/news200/4.m4s" mediaRange="25622-33693"/>
          </SegmentList>
        </Representation>

        <Representation id="1" codecs="avc1" mimeType="video/mp4" width="320" height="240" startWithSAP="1" bandwidth="270370">
          <SegmentBase>
            <Initialization sourceURL="main/news300/1.m4s" range="0-865"/>
          </SegmentBase>
          <SegmentList duration="1">
            <SegmentURL media="main/news300/2.m4s" mediaRange="866-26970"/>
            <SegmentURL media="main/news300/3.m4s" mediaRange="26971-72543"/>
            <SegmentURL media="main/news300/4.m4s" mediaRange="72544-95972"/>
          </SegmentList>
        </Representation>

        …

      </AdaptationSet>
    </Period>
  </MPD>
```

The MPD file tells the browser where the various pieces of media are located, it also includes metadata such as mimeType and codecs and there are other details such as byte-ranges in there too. Generally these files will be generated for you.

**Note:** You can also split out your audio and video streams into separate files, which can then be prioritized and served separately depending on bandwidth.

Once you have generated your MPD file you can reference as expected it from within the `<video>` element:

```html
<video src="my.mpd" type="application/dash+xml"></video>
```

it might be wise to provide a fallback:

```html
<video>
  <source src="my.mpd" type="application/dash+xml" />
  <!-- fallback -->
  <source src="my.mp4" type="video/mp4" />
  <source src="my.webm" type="video/webm" />
</video>
```

**Note:** MPEG-DASH playback relies on dash.js and browser support for Media Source Extensions, see the latest dash.js reference player.

HLS Encoding
------------

HTTP Live Streaming (HLS) is an HTTP-based media streaming protocol implemented by Apple. It's incorporated into iOS and OSX platforms and works well on mobile and desktop Safari and most Android devices with some caveats.

Media is usually encoded as MPEG-4 (H.264 video and AAC audio) and packaged into an MPEG-2 Transport Stream, which is then broken into segments and saved as one or more `.ts` media files. Apple provides tools to convert media files to the appropriate format.

### HLS encoding tools

There are a number of useful tools available for HLS encoding

* The Stream Segmenter — provided by Apple for Mac platforms — takes a media stream from a local network and splits media into equally sized media files together with an index file.
* Apple also provides a File Segmenter for Mac — which takes a suitably encoded file, splits it up and produces an index file, in a similar fashion to the Stream Segmenter.

**Note:** You can find more details about these tools at Using HTTP Live Streaming.

### Index Files (Playlists)

The HLS Index File (much like MPEG-DASH's `.mpd` file) contains the information on where all the media segments reside, as well as other metadata such as bandwidth application. Apple uses the `.m3u8` format (an extension of its `.m3u` playlist format) for index files — see below for an example:
```

#EXT-X-VERSION:3
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:1

# Old-style integer duration; avoid for newer clients.
#EXTINF:10,
http://media.example.com/segment0.ts

# New-style floating-point duration; use for modern clients.
#EXTINF:10.0,
http://media.example.com/segment1.ts
#EXTINF:9.5,
http://media.example.com/segment2.ts
#EXT-X-ENDLIST
```

**Note:** Comprehensive information on how to encode media for Apple's HLS format can be found on Apple's Developer Pages.

See also
--------

Further resources on adaptive streaming.

### General information

* Adaptive Streaming in the Field
### HLS overview and references

* HTTP Live Streaming
* What is HLS (HTTP-Live-Streaming)?
* HTTP Live Streaming Overview
### MPEG-DASH overview and references

* Dynamic Adaptive Streaming over HTTP Dataset
* MPEG-DASH and streaming reference and resources (MSDN)
* DASH Adaptive Streaming for HTML 5 Video
* Dynamic Adaptive Streaming over HTTP: From Content Creation to Consumption
### MPEG-DASH tools

* DASHEncoder
* MP4Box
* DASH.js Wiki
* DASH.js Google Group
* Akamai Dash Diagnostic Player

Adaptive streaming examples

* ITEC – Dynamic Adaptive Streaming over HTTP
* MPEG DASH Media Source Demo


Creating a cross-browser video player - Web media technologies

Creating a cross-browser video player
=====================================

This article describes a simple HTML video player that uses the Media and Fullscreen APIs and works across most major desktop and mobile browsers. As well as working fullscreen, the player features custom controls rather than just using the browser defaults. The player controls themselves won't be styled beyond the basics required to get them working; full styling of the player will be taken care of in a future article.

Working example
---------------

Our example video player displays a clip from an open source movie called Tears of Steel, and includes typical video controls.

![a shot of a video player, with several control buttons such as play, pause and stop. The video is showing a group of men fighting a group of robots.](/en-US/docs/Web/Media/Audio_and_video_delivery/cross_browser_video_player/video-player-example.png)

**Note:** You can see the example running live, or check out the source code on GitHub.

HTML Markup
-----------

To start off with, let's take a look at the HTML that makes up the player.

### The video

First of all the `<video>` element is defined, contained within a `<figure>` element that acts as the video container. To anyone familiar with HTML markup and the `<video>` element, there should be nothing here that surprises you.

```html
<figure id="videoContainer">
  <video id="video" controls preload="metadata" poster="img/poster.jpg">
    <source
 src="video/tears-of-steel-battle-clip-medium.mp4"
 type="video/mp4" />
    <source
 src="video/tears-of-steel-battle-clip-medium.webm"
 type="video/webm" />
    <source
 src="video/tears-of-steel-battle-clip-medium.ogg"
 type="video/ogg" />
    <!-- Offer download -->
    <a href="video/tears-of-steel-battle-clip-medium.mp4">Download MP4</a>
  </video>
  <figcaption>
    © Blender Foundation |
    <a href="http://mango.blender.org">mango.blender.org</a>
  </figcaption>
</figure>
```

Even though this player will define its own custom control set, the `controls` attribute is still added to the `<video>` element, and the player's default control set is switched off later with JavaScript. Doing things this way still allows users who have JavaScript turned off (for whatever reason) to still have access to the browser's native controls.

A poster image is defined for the video, and the `preload` attribute is set to `metadata`, which informs the browser that it should initially only attempt to load the metadata from the video file rather than the entire video file. This provides the player with data such as video duration and format.

**Note:** IE9 behaves differently than most other browsers when a `poster` attribute is set. Most browsers interpret the presence of a `poster` attribute to mean that the specified image is to be displayed until the user chooses to play the video. IE9 will only use the specified poster image in this way if `preload="none"` is set; otherwise, it will take the first still of the video and display that instead.

Three different video sources are provided for the player: MP4, WebM, and Ogg. Using these different source formats gives the best chance of being supported across all browsers that support HTML video. For further information on video formats and browser compatibility, see supported media formats.

The code above would allow playback of the video in most browsers, using the browser's default control set. The next step is to define a custom control set, also in HTML, which will be used to control the video.

### The control set

Most browser's default video controls have the following functionality:

* Play/pause
* Mute
* Volume control
* Progress bar
* Skip ahead
* Go fullscreen

The custom control set will also support this functionality, with the addition of a stop button.

Once again the HTML is quite straightforward, using an unordered list with `list-style-type:none` set to enclose the controls, each of which is a list item with `float:left`. For the progress bar, the `progress` element is taken advantage of, with a fallback provided for browsers that don't support it (e.g. IE8 and IE9). This list is inserted after the `<video>` element, but inside the `<figure>` element (this is important for the fullscreen functionality, which is explained later on).

```html
<ul id="video-controls" class="controls">
  <li><button id="playpause" type="button">Play/Pause</button></li>
  <li><button id="stop" type="button">Stop</button></li>
  <li class="progress">
    <progress id="progress" value="0" min="0">
      <span id="progress-bar"></span>
    </progress>
  </li>
  <li><button id="mute" type="button">Mute/Unmute</button></li>
  <li><button id="volinc" type="button">Vol+</button></li>
  <li><button id="voldec" type="button">Vol-</button></li>
  <li><button id="fs" type="button">Fullscreen</button></li>
</ul>
```

Each button is given an `id` so it can be easily accessed with JavaScript. The `span` within the `<progress>` element is for browsers that do not support the `progress` element and will be updated at the same time as `progress` (this `span` element won't be visible on browsers that support `progress`).

The controls are initially hidden with a CSS `display:none` and will be enabled with JavaScript. Again if a user has JavaScript disabled, the custom control set will not appear and they can use the browser's default control set unhindered.

Of course this custom control set is currently useless and doesn't do a thing: Let's improve the situation with JavaScript.

Using the Media API
-------------------

HTML comes with a JavaScript Media API that allows developers access to and control of HTML media. This API will be used to make the custom control set defined above actually do something. In addition, the fullscreen button will use the Fullscreen API, another W3C API that controls the ability of web browsers to show apps using your computer's full screen.

### Setup

Before dealing with the individual buttons, a number of initialization calls are required.

To begin with, it's a good idea to first check if the browser actually supports the `<video>` element and to only set up the custom controls if it does. This is done by checking if a created `<video>` element supports the `canPlayType()` method, which any supported HTML `<video>` element should.

```js
const supportsVideo = !!document.createElement("video").canPlayType;
if (supportsVideo) {
  // set up custom controls
  // …
}
```

Once it has been confirmed that the browser does indeed support HTML video, it's time to set up the custom controls. A number of variables pointing to HTML elements are required:

```js
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");
```

As mentioned earlier, the browser's default controls now need to be disabled, and the custom controls need to be displayed:

```js
// Hide the default controls
video.controls = false;

// Display the user defined video controls
videoControls.style.display = "block";
```

With that done, a variable pointing to each of the buttons is now required:

```js
const playpause = document.getElementById("playpause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volinc = document.getElementById("volinc");
const voldec = document.getElementById("voldec");
const progress = document.getElementById("progress");
const progressBar = document.getElementById("progress-bar");
const fullscreen = document.getElementById("fs");
```

Using these handles, events can now be attached to each of the custom control buttons making them interactive. Most of these buttons require a simple `click` event listener to be added, and a Media API defined method and/or attributes to be called/checked on the video.

### Play/Pause

```js
playpause.addEventListener("click", (e) => {
  if (video.paused || video.ended) {
    video.play();
  } else {
    video.pause();
  }
});
```

When a `click` event is detected on the play/pause button, the handler first of all checks if the video is currently paused or has ended (via the Media API's `paused` and `ended` attributes); if so, it uses the `play()` method to play back the video. Otherwise the video must be playing, so it is paused using the `pause()` method.

### Stop

```js
stop.addEventListener("click", (e) => {
  video.pause();
  video.currentTime = 0;
  progress.value = 0;
});
```

The Media API doesn't have a `stop` method, so to mimic this the video is paused, and its `currentTime` (i.e. the video's current playing position) and the `<progress>` element's position is set to 0 (more on the `<progress>` element later).

### Mute

```js
mute.addEventListener("click", (e) => {
  video.muted = !video.muted;
});
```

The mute button is a simple toggle button that uses the Media API's `muted` attribute to mute the video: this is a Boolean indicating whether the video is muted or not. To get it to toggle, we set it to the inverse of itself.

### Volume

```js
volinc.addEventListener("click", (e) => {
  alterVolume("+");
});
voldec.addEventListener("click", (e) => {
  alterVolume("-");
});
```

Two volume control buttons have been defined, one for increasing the volume and another for decreasing it. A user defined function, `alterVolume(direction)` has been created that deals with this:

```js
function alterVolume(dir) {
  const currentVolume = Math.floor(video.volume \* 10) / 10;
  if (dir === "+" && currentVolume < 1) {
    video.volume += 0.1;
  } else if (dir === "-" && currentVolume > 0) {
    video.volume -= 0.1;
  }
}
```

This function makes use of the Media API's `volume` attribute, which holds the current volume value of the video. Valid values for this attribute are 0 and 1 and anything in between. The function checks the `dir` parameter, which indicates whether the volume is to be increased (+) or decreased (-) and acts accordingly. The function is defined to increase or decrease the video's `volume` attribute in steps of 0.1, ensuring that it doesn't go lower than 0 or higher than 1.

### Progress

When the `<progress>` element was defined above in the HTML, only two attributes were set, `value` and `min`, both being given a value of 0. The function of these attributes is self-explanatory, with `min` indicating the minimum allowed value of the `progress` element and `value` indicating its current value. It also needs to have a maximum value set so that it can display its range correctly, and this can be done via the `max` attribute, which needs to be set to the maximum playing time of the video. This is obtained from the video's `duration` attribute, which again is part of the Media API.

Ideally, the correct value of the video's `duration` attribute is available when the `loadedmetadata` event is raised, which occurs when the video's metadata has been loaded:

```js
video.addEventListener("loadedmetadata", () => {
  progress.setAttribute("max", video.duration);
});
```

Unfortunately in some mobile browsers, when `loadedmetadata` is raised — if it even *is* raised — `video.duration` may not have the correct value, or even any value at all. So something else needs to be done. More on that below.

Another event, `timeupdate`, is raised periodically as the video is being played through. This event is ideal for updating the progress bar's value, setting it to the value of the video's `currentTime` attribute, which indicates how far through the video the current playback is.

```js
video.addEventListener("timeupdate", () => {
  progress.value = video.currentTime;
  progressBar.style.width = `${Math.floor(
 (video.currentTime \* 100) / video.duration,
 )}%`;
});
```

As the `timeupdate` event is raised, the `progress` element's `value` attribute is set to the video's `currentTime`. The `<span>` element mentioned earlier, for browsers that do not support the `<progress>` element, is also updated at this time, setting its width to be a percentage of the total time played. This span has a solid CSS background color, which helps it provide the same visual feedback as a `<progress>` element.

Coming back to the `video.duration` problem mentioned above, when the `timeupdate` event is raised, in most mobile browsers the video's `duration` attribute should now have the correct value. This can be taken advantage of to set the `progress` element's `max` attribute if it is currently not set:

```js
video.addEventListener("timeupdate", () => {
  if (!progress.getAttribute("max"))
    progress.setAttribute("max", video.duration);
  progress.value = video.currentTime;
  progressBar.style.width = `${Math.floor(
 (video.currentTime \* 100) / video.duration,
 )}%`;
});
```

**Note:** for more information and ideas on progress bars and buffering feedback, read Media buffering, seeking, and time ranges.

### Skip Ahead

Another feature of most browser default video control sets is the ability to click on the video's progress bar to "skip ahead" to a different point in the video. This can also be achieved by adding a simple `click` event listener to the `progress` element:

```js
progress.addEventListener("click", (e) => {
  const rect = progress.getBoundingClientRect();
  const pos = (e.pageX - rect.left) / progress.offsetWidth;
  video.currentTime = pos \* video.duration;
});
```

This piece of code uses the clicked position to (roughly) work out where in the `progress` element the user has clicked, and to move the video to that position by setting its `currentTime` attribute.

### Fullscreen

The Fullscreen API should be straight forward to use: the user clicks a button, if the video is in fullscreen mode: cancel it, otherwise enter fullscreen mode.

Alas it has been implemented in browsers in a number of weird and wonderful ways which requires a lot of extra code to check for various prefixed versions of attributes and methods to call the right one.

The visibility of the fullscreen button depends on whether the browser supports the Fullscreen API and that it is enabled:

```js
if (!document?.fullscreenEnabled) {
  fullscreen.style.display = "none";
}
```

Naturally the fullscreen button needs to actually do something, so, like the other buttons, a `click` event handler is attached in which we call a user defined function `handleFullscreen`:

```js
fullscreen.addEventListener("click", (e) => {
  handleFullscreen();
});
```

The `handleFullscreen` function is defined as follows:

```js
function handleFullscreen() {
  if (document.fullscreenElement !== null) {
    // The document is in fullscreen mode
    document.exitFullscreen();
    setFullscreenData(false);
  } else {
    // The document is not in fullscreen mode
    videoContainer.requestFullscreen();
    setFullscreenData(true);
  }
}
```

If the browser is currently in fullscreen mode, then it must be exited and vice versa. Interestingly `document` must be used for exiting/cancelling fullscreen mode, whereas any HTML element can request fullscreen mode, here the `videoContainer` is used as it also contains the custom controls which should also appear with the video in fullscreen mode.

Another user defined function — `setFullscreenData()` — is also called, which sets the value of a `data-fullscreen` attribute on the `videoContainer` (this makes use of `data-states`).

```js
function setFullscreenData(state) {
  videoContainer.setAttribute("data-fullscreen", !!state);
}
```

This is used to set some basic CSS to improve the styling of the custom controls when they are in fullscreen (see the sample code for further details). When a video goes into fullscreen mode, it usually displays a message indicating that the user can press the *Esc* key to exit fullscreen mode, so the code also needs to listen for relevant events in order to call the `setFullscreenData()` function to ensure the control styling is correct:

```js
document.addEventListener("fullscreenchange", (e) => {
  setFullscreenData(!!document.fullscreenElement);
});
```

See also
--------

* `<video>` for reference material
* Using HTML audio and video for more techniques
* Media formats supported by the HTML audio and video elements
* Video for Everybody: written by Kroc Camen, this is quite old, but still has some good relevant content and is a great starter article for cross-browser HTML video.