← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Media APIs

Media APIs

Controlling audio and video in the browser — HTMLMediaElement, Web Audio API, MediaStream, getUserMedia, and the MediaRecorder API.

What it is, in plain English: The Media APIs in the browser cover playing, controlling, and creating audio and video. HTMLMediaElement (the interface behind
Where this bites you

play() returns a Promise, and browsers reject it when a page tries to start media with sound before the user has interacted with the page (autoplay policy). Code that calls play() and carries on as if it worked gets an unhandled NotAllowedError. The player UI then lies to the user: the button shows "pause" while nothing is playing.

// <audio src="tone.wav"></audio> <button id="play">▶</button>
const media   = document.querySelector("audio");
const playBtn = document.querySelector("#play");

media.play();                       // start on page load, before any click
playBtn.textContent = "⏸";          // assumes it worked
console.log("button shows:", playBtn.textContent, "| paused:", media.paused);
// button shows: ⏸ | paused: true
// Uncaught (in promise) NotAllowedError: play() failed because the user didn't interact with the document first.

Playing and controlling audio and video

HTMLMediaElement — the video and audio API

const video = document.querySelector("video");

// Playback control:
video.play();           // returns a Promise
video.pause();
video.currentTime = 0;  // seek to start (in seconds)
video.volume = 0.7;     // 0 to 1
video.muted  = true;
video.playbackRate = 2; // 2x speed

// State:
video.paused;           // true/false
video.ended;            // true/false
video.duration;         // total seconds (NaN until metadata loaded)
video.readyState;       // 0-4 (4 = can play through)

// Events:
video.addEventListener("play",       () => console.log("Playing"));
video.addEventListener("pause",      () => console.log("Paused"));
video.addEventListener("ended",      () => console.log("Ended"));
video.addEventListener("timeupdate", () => {
  const pct = (video.currentTime / video.duration * 100).toFixed(1);
  progressBar.style.width = pct + "%";
});
video.addEventListener("error",      (e) => console.error("Error:", e.target.error));

Building a custom video player

const video   = document.querySelector("video");
const playBtn = document.querySelector("#play");

playBtn.addEventListener("click", () => {
  if (video.paused) {
    video.play();
    playBtn.textContent = "⏸";
  } else {
    video.pause();
    playBtn.textContent = "▶";
  }
});

// Keyboard controls:
document.addEventListener("keydown", (e) => {
  if (e.key === " ")          video.paused ? video.play() : video.pause();
  if (e.key === "ArrowLeft")  video.currentTime -= 10;
  if (e.key === "ArrowRight") video.currentTime += 10;
  if (e.key === "ArrowUp")    video.volume = Math.min(1, video.volume + 0.1);
  if (e.key === "ArrowDown")  video.volume = Math.max(0, video.volume - 0.1);
});

The custom player above sets the button right after calling play(), so it has the same weakness whenever playback fails. Await the Promise and handle rejection. Better still, let the element's own play and pause events drive the button, so it always shows what really happened. (Muted media is generally allowed to autoplay, which is why background videos use muted.)

const media   = document.querySelector("audio");
const playBtn = document.querySelector("#play");

media.addEventListener("play",  () => { playBtn.textContent = "⏸"; });   // the button follows
media.addEventListener("pause", () => { playBtn.textContent = "▶"; });   // what really happened

async function tryPlay() {
  try {
    await media.play();             // play() returns a Promise: wait for it
  } catch (err) {
    console.log("blocked:", err.name);   // NotAllowedError until the user interacts
  }
  console.log("button shows:", playBtn.textContent, "| paused:", media.paused);
}
tryPlay();
// blocked: NotAllowedError
// button shows: ▶ | paused: true
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.