13 min read

HTML Audio: A Complete Guide to the Basic APIs

Table of Contents

Cover Image

Understand playback, loading, buffering, and the events that keep an audio interface in sync.

Adapted from the original article by zhangxinxu, with technical clarifications and practical excerpts from the supplied demo.

Adding audio to a webpage takes one HTML element. Building a reliable player requires understanding what happens after someone presses Play.

When is the audio ready? What happens if playback is blocked? How do you distinguish a paused track from one that is buffering?

The <audio> element provides properties, methods, and events for answering these questions. Most of these APIs also apply to <video>.

One detail about the companion demo: it demonstrates custom event dispatch and interface updates, rather than audio playback. We’ll use three excerpts to explore event-driven UI techniques that are useful when building a custom player.

1. Start with the Audio Element

For a basic player, use <audio src="audiofile.mp3" controls></audio>.

The src attribute identifies the media file, and controls asks the browser to display its built-in player.

You can also supply multiple formats using nested <source> elements. Each source can declare a MIME type, allowing the browser to skip formats it knows it cannot play.

For example, a source entry might be written as <source src="audiofile.mp3" type="audio/mpeg">.

The type attribute belongs on <source>, not <audio>. It describes the candidate resource and helps the browser select an appropriate source. MDN’s source element reference

Start with native controls when they meet your needs. Their appearance varies across browsers, but they provide a useful baseline before you build a custom interface.

2. Understand the HTML Attributes

The main audio attributes control resource selection, presentation, and initial playback behavior.

AttributePurpose
srcSpecifies the media resource URL.
controlsDisplays the browser’s playback controls.
autoplayRequests automatic playback, subject to browser policy.
loopRestarts playback when the track reaches its end.
mutedSets the initial muted state.
preloadSuggests how much media to load before playback.

controls, autoplay, loop, and muted are Boolean attributes: their presence enables them. Writing autoplay="false" still includes the attribute; remove it to disable the request.

Autoplay Is a Request

Adding autoplay does not guarantee that audible playback will begin.

Browsers restrict automatic playback according to their policies and the user’s interaction with a site. Chrome, for example, considers factors including user interaction and, on desktop, media engagement. Muted autoplay receives different treatment from audible playback. Chrome’s autoplay policy

For a predictable experience, provide an explicit Play control and handle playback failures.

Looping and Muting

You can change these settings through JavaScript:

  • audio.loop = true enables repeated playback.
  • audio.muted = true silences the audio.
  • audio.muted = false restores its unmuted state.

Looping is useful for repeating clips or ambient sound, provided the interface gives listeners a clear way to stop playback.

Preload Balances Readiness and Bandwidth

The preload attribute accepts three main values:

ValueIntended behavior
noneAvoid preloading the media.
metadataFetch information such as duration.
autoAllow broader preloading, potentially including the entire file.

Preload is a hint, not a download guarantee. The browser decides how much data to fetch. Choose a value based on how likely playback is and how much data the page contains. HTML Standard: loading media resources

Practical Example: Create a Visible Event Interface

Custom controls need places to display status and feedback. This HTML, extracted from the supplied demo, provides an input, a dispatch button, a status card, and an event log.

Use it with the demo’s existing stylesheet to reproduce the styled interface.

<div class="controls">
  <input id="payload" value="Hello from a CustomEvent">
  <button id="dispatch">Dispatch event</button>
</div>

<div class="event-card" id="latest">Waiting for an event...</div>
<div class="log" id="event-log">No events yet.</div>

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

The IDs provide stable targets for JavaScript. The interface is initially static; the next two examples add behavior.

In an audio player, comparable display areas could show “Loading,” “Playing,” or “Playback failed.” This excerpt itself does not create an audio player.

3. Read and Change Playback Properties

Once you have an audio element, select it explicitly—for example, with const audio = document.querySelector('audio').

Its properties expose the current playback state.

currentTime: Read or Change the Position

audio.currentTime returns the playback position in seconds.

Assigning a value requests a seek. For example, audio.currentTime = 5 moves playback to the five-second mark when that position is available.

This property powers progress indicators, skip buttons, and chapter navigation.

duration: Find the Track Length

audio.duration reports the media duration in seconds.

Before duration information becomes available, it can be NaN. For an unbounded stream, it can be Infinity. Check Number.isFinite(audio.duration) before calculating a percentage. HTML Standard: media offsets

A custom player can display a placeholder until it has a usable duration.

volume and muted: Separate Controls

audio.volume represents the volume setting on a scale from 0 to 1. Setting audio.volume = 0.5 selects a 50% volume level.

Muting is separate. If the volume is 0.5, setting audio.muted = true silences playback without changing that stored volume value. Unmuting can therefore restore the previous level. HTML Standard: media volume and muting

playbackRate: Change the Speed

audio.playbackRate = 1.5 requests playback at one and a half times the normal speed.

A value of 1 is normal speed; 0.5 is half speed. Supported extremes and audio behavior vary across browsers, so test the speed options you expose. MDN’s playbackRate reference

paused: Read the Pause State

audio.paused is read-only. It is initially true and becomes true again when playback is paused.

It does not describe every reason audio might be silent. Muting, buffering, and playback position are separate parts of the media state.

4. Control Playback with JavaScript Methods

play(): Handle the Promise

Calling audio.play() requests playback and returns a Promise.

That Promise resolves when playback starts. It can reject if playback is disallowed or cannot begin, so the interface should handle failure rather than immediately assume success.

Calling pause() while a playback request is still pending can also interrupt that request and cause rejection. A fixed timeout does not reliably solve the race. When an operation depends on playback having started, sequence it after the Promise resolves. Chrome’s explanation of interrupted play requests

pause(): Pause Without Resetting

audio.pause() pauses playback while preserving the current position.

There is no built-in stop() method. For an ordinary file, a Stop control can call audio.pause() and then set audio.currentTime = 0.

canPlayType(): Check Format Support

Calling audio.canPlayType('audio/mpeg') returns one of three strings:

  • "probably": the browser expects to support the type.
  • "maybe": playback might be supported.
  • "": the browser reports no support.

A nonempty result is useful for source selection, but it does not guarantee that a particular file will load and decode successfully.

load(): Restart Resource Selection

audio.load() resets the element and starts selecting and loading its media resource again. It is useful after changing nested <source> entries.

It can abort ongoing operations, including pending playback requests. The amount subsequently fetched still depends on preload behavior and browser decisions. MDN’s load method reference

Practical Example: Dispatch a Custom UI Event

The supplied demo separates the button click from the code that renders its result. This excerpt reads the input and sends a custom show event containing a message and timestamp.

Place this script after the demo markup. The screenshot represents the completed demo, including the listener introduced later.

const input = document.querySelector('#payload');

document.querySelector('#dispatch').addEventListener('click', () => {
  const message = input.value.trim() || 'Default CustomEvent payload';

  const event = new CustomEvent('show', {
    detail: { message, sentAt: new Date().toLocaleTimeString() }
  });

  window.dispatchEvent(event);
});

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

CustomEvent.detail carries application-defined data. The click handler produces that data without directly updating the card or log.

The event name show is specific to this demo. Dispatching it does not start audio playback or create the trusted user interaction required by browser playback policies.

5. Follow the Media Loading Lifecycle

Media loading exposes several milestones. Each answers a different question about the resource.

EventWhat it tells you
loadstartThe browser has begun loading the resource.
durationchangeThe reported duration has changed.
loadedmetadataMetadata, such as duration and track information, is available.
loadeddataMedia data at the current playback position is available for the first time.
progressThe browser is fetching media data.
canplayEnough data is available to begin or resume playback.
canplaythroughThe browser estimates that playback can continue to the end without buffering.

loadedmetadata and loadeddata are distinct. The latter does not merely mean that the first byte has arrived; it concerns media data at the current playback position. MDN’s loadeddata event reference

A useful conceptual progression is resource selection, metadata availability, playable data, and sufficient buffering. Avoid implementing the interface as a rigid checklist: events such as progress can recur, and readiness can change.

Loading Interruptions Need Different Responses

Several events describe interrupted or suspended loading:

EventMeaning
suspendThe browser has intentionally stopped fetching data for now.
abortLoading was aborted without a media error.
errorResource loading or playback encountered an error.
emptiedThe media element has returned to an empty state, such as during reinitialization.
stalledThe browser is trying to fetch data but is not receiving it as expected.

A suspend event does not automatically mean something is broken. The browser may simply have buffered enough.

Likewise, canplaythrough is an estimate. Network conditions can change after it fires.

6. Keep the Interface in Sync with Playback Events

Loading and playback are related, but they are separate parts of the player’s state.

EventTypical interface response
playReflect that the element has left its paused state.
playingShow active playback and clear a buffering indicator.
waitingShow a temporary buffering state.
pauseShow the paused state.
timeupdateRefresh elapsed time and playback progress.
endedShow completion or advance to another track.
volumechangeUpdate volume and mute controls.
ratechangeUpdate the displayed playback speed.

The distinction between play and playing matters. Leaving the paused state does not necessarily mean media is already advancing. playing fires when playback starts or resumes after a delay; waiting signals a temporary lack of data. MDN’s playing event reference, waiting event reference

timeupdate Is Not a Precise Timer

Use timeupdate to refresh progress displays, but do not assume it fires exactly every 250 milliseconds. Its frequency varies with system load and event-handler cost. Read the current audio.currentTime value on each update. MDN’s timeupdate event reference

Practical Example: Render Event Data and Recent History

This final excerpt completes the supplied demo. It listens for show, updates the latest-message card, and displays the six most recent entries.

Run it after the markup, alongside the dispatch code.

const latest = document.querySelector('#latest');
const log = document.querySelector('#event-log');
const history = [];

window.addEventListener('show', (event) => {
  const detail = event.detail || {};

  latest.textContent = detail.message + ' | sent at ' + detail.sentAt;
  history.unshift('[' + detail.sentAt + '] ' + detail.message);
  log.textContent = history.slice(0, 6).join('\n');
});

Demo animation

🎮 Try it live: Open the interactive demo to experience this yourself.

The listener handles presentation independently of the button. Using textContent displays the message as text, while the demo stylesheet’s white-space: pre-wrap preserves the log’s line breaks.

Notice that slice(0, 6) limits the visible entries; it does not limit the stored array.

For an actual audio player, attach listeners to the audio element and read its properties. Native media events such as timeupdate do not provide the demo’s custom detail.message payload.

7. Understand Buffering and Seeking

A progress bar can represent several different things:

  • Playback position: where the listener is now.
  • Buffered ranges: which parts are already available locally.
  • Seekable ranges: which positions the browser can seek to.

Confusing these values can make a player display misleading progress.

buffered: What Has Been Loaded

audio.buffered returns a TimeRanges object describing buffered portions of the media.

It may contain no ranges, one continuous range, or several separate ranges. To inspect it, use:

  • audio.buffered.length
  • audio.buffered.start(index)
  • audio.buffered.end(index)

Check the length before accessing a range.

seekable: Where Playback Can Move

audio.seekable also returns TimeRanges, but describes the positions available for seeking.

Seekable content is not necessarily already buffered. A browser may be able to request another part of a resource when the listener jumps forward. MDN’s seekable property reference

seeking and seeked: Track Position Changes

seeking indicates that a seek operation has started. seeked indicates that it has completed.

These events concern changing playback position—not ordinary network requests. They can help a custom interface explain the brief delay between dragging a slider and resuming playback. HTML Standard: media event summary

8. Build Around the Player’s Actual State

A dependable audio interface stays aligned with the media element.

Use HTML attributes for initial configuration, properties to inspect or change state, methods to request actions, and events to update the interface as those actions take effect.

The companion demo makes that event-driven structure visible: one handler produces an event, another renders its data, and a log records what happened. When applying the pattern to audio, let the media element’s events and playback Promise tell you what actually occurred.

Start with native controls, add custom behavior where it serves the listener, and make every loading, playback, and error state understandable.


Try It Yourself

Want to see these concepts in action? I’ve created an interactive demo where you can experiment with the code and see real-time results.

View the Live Demo

Explore more demos from my previous articles in the Demo Gallery.

Happy coding!