Content & Media

Free Audio Player Widget

A clean, custom single-track audio player with seek, time, and speed controls.

A lightweight custom audio player for embedding a single track — music, interviews, sermons, or samples — anywhere on your site. It includes play/pause, a seek bar, elapsed and total time, and a playback speed selector. Install it with a single script tag and point it at your audio file.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/audio-player.js" data-hl-src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" data-hl-title="SoundHelix Song 1" data-hl-artist="SoundHelix" async></script>
<!-- Free Audio Player widget by haynes lab — https://hayneslab.dev/tools/widgets/audio-player -->
Inline (self-contained)html
<!-- Free Audio Player widget by haynes lab — https://hayneslab.dev/tools/widgets/audio-player -->
<script data-hl-src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" data-hl-title="SoundHelix Song 1" data-hl-artist="SoundHelix">
/*!
 * haynes lab — Audio Player widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/audio-player
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-audio-player';
  var STYLE_ID = 'hlw-audio-player-styles';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/audio-player.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 480px; margin: 0 auto; padding: 18px 20px 26px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__meta { margin-bottom: 14px; min-width: 0; }' +
      '.' + PREFIX + '__title { font-weight: 600; font-size: 16px; color: #111827;' +
      ' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }' +
      '.' + PREFIX + '__artist { font-size: 13px; color: #6b7280; margin-top: 2px;' +
      ' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }' +
      '.' + PREFIX + '__controls { display: flex; align-items: center; gap: 12px; }' +
      '.' + PREFIX + '__play { flex: 0 0 auto; width: 44px; height: 44px; border: 0; border-radius: 50%;' +
      ' background: var(--hlw-accent); color: #ffffff; cursor: pointer; padding: 0;' +
      ' display: flex; align-items: center; justify-content: center; }' +
      '.' + PREFIX + '__play:hover { filter: brightness(1.1); }' +
      '.' + PREFIX + '__play:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__play svg { display: block; }' +
      '.' + PREFIX + '__middle { flex: 1 1 auto; min-width: 0; }' +
      '.' + PREFIX + '__seek { display: block; width: 100%; height: 6px; margin: 0; cursor: pointer;' +
      ' accent-color: var(--hlw-accent); }' +
      '.' + PREFIX + '__seek:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__times { display: flex; justify-content: space-between; margin-top: 4px;' +
      ' font-size: 12px; color: #6b7280; font-variant-numeric: tabular-nums; }' +
      '.' + PREFIX + '__speed { flex: 0 0 auto; padding: 4px 6px; font-size: 13px; color: #374151;' +
      ' background: #ffffff; border: 1px solid #e5e7eb; border-radius: 8px; cursor: pointer;' +
      ' font-family: inherit; }' +
      '.' + PREFIX + '__speed:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
      ' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function iconSvg(kind) {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '20');
    svg.setAttribute('height', '20');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    svg.setAttribute('fill', 'currentColor');
    var path = document.createElementNS(svgNS, 'path');
    if (kind === 'play') {
      path.setAttribute('d', 'M8 5.5v13c0 .8.9 1.3 1.6.9l10-6.5c.6-.4.6-1.4 0-1.8l-10-6.5C8.9 4.2 8 4.7 8 5.5z');
    } else {
      path.setAttribute('d', 'M7 5h3.5v14H7zM13.5 5H17v14h-3.5z');
    }
    svg.appendChild(path);
    return svg;
  }

  function formatTime(seconds) {
    if (!isFinite(seconds) || seconds < 0) return '0:00';
    var total = Math.floor(seconds);
    var m = Math.floor(total / 60);
    var s = total % 60;
    return m + ':' + (s < 10 ? '0' : '') + s;
  }

  function build(script) {
    var src = script.getAttribute('data-hl-src');
    if (!src) {
      console.warn('[haynes lab audio-player] Missing required data-hl-src attribute (URL of the audio file). Nothing rendered.');
      return;
    }
    var title = script.getAttribute('data-hl-title') || 'Audio track';
    var artist = script.getAttribute('data-hl-artist') || '';
    var accent = script.getAttribute('data-hl-accent') || '#f97316';

    injectStyles();

    var root = el('div', PREFIX);
    root.setAttribute('role', 'region');
    root.setAttribute('aria-label', 'Audio player');
    root.style.setProperty('--hlw-accent', accent);

    var audio = document.createElement('audio');
    audio.preload = 'metadata';
    audio.src = src;
    root.appendChild(audio);

    var meta = el('div', PREFIX + '__meta');
    meta.appendChild(el('div', PREFIX + '__title', title));
    if (artist) meta.appendChild(el('div', PREFIX + '__artist', artist));
    root.appendChild(meta);

    var controls = el('div', PREFIX + '__controls');

    var playBtn = el('button', PREFIX + '__play');
    playBtn.type = 'button';
    playBtn.setAttribute('aria-label', 'Play');
    playBtn.appendChild(iconSvg('play'));

    var middle = el('div', PREFIX + '__middle');
    var seek = document.createElement('input');
    seek.type = 'range';
    seek.className = PREFIX + '__seek';
    seek.min = '0';
    seek.max = '1000';
    seek.value = '0';
    seek.step = '1';
    seek.setAttribute('aria-label', 'Seek');

    var times = el('div', PREFIX + '__times');
    var currentEl = el('span', null, '0:00');
    var durationEl = el('span', null, '0:00');
    times.appendChild(currentEl);
    times.appendChild(durationEl);
    middle.appendChild(seek);
    middle.appendChild(times);

    var speed = document.createElement('select');
    speed.className = PREFIX + '__speed';
    speed.setAttribute('aria-label', 'Playback speed');
    var rates = ['0.5', '0.75', '1', '1.25', '1.5', '2'];
    rates.forEach(function (r) {
      var opt = document.createElement('option');
      opt.value = r;
      opt.textContent = r + '×';
      if (r === '1') opt.selected = true;
      speed.appendChild(opt);
    });

    controls.appendChild(playBtn);
    controls.appendChild(middle);
    controls.appendChild(speed);
    root.appendChild(controls);

    var seeking = false;

    function setPlayState(playing) {
      playBtn.setAttribute('aria-label', playing ? 'Pause' : 'Play');
      while (playBtn.firstChild) playBtn.removeChild(playBtn.firstChild);
      playBtn.appendChild(iconSvg(playing ? 'pause' : 'play'));
    }

    playBtn.addEventListener('click', function () {
      if (audio.paused) {
        var p = audio.play();
        if (p && typeof p.catch === 'function') {
          p.catch(function () { setPlayState(false); });
        }
      } else {
        audio.pause();
      }
    });

    audio.addEventListener('play', function () { setPlayState(true); });
    audio.addEventListener('pause', function () { setPlayState(false); });
    audio.addEventListener('ended', function () { setPlayState(false); });

    audio.addEventListener('loadedmetadata', function () {
      durationEl.textContent = formatTime(audio.duration);
    });

    audio.addEventListener('timeupdate', function () {
      currentEl.textContent = formatTime(audio.currentTime);
      if (!seeking && isFinite(audio.duration) && audio.duration > 0) {
        seek.value = String(Math.round((audio.currentTime / audio.duration) * 1000));
      }
    });

    seek.addEventListener('input', function () {
      seeking = true;
      if (isFinite(audio.duration) && audio.duration > 0) {
        currentEl.textContent = formatTime((Number(seek.value) / 1000) * audio.duration);
      }
    });
    seek.addEventListener('change', function () {
      if (isFinite(audio.duration) && audio.duration > 0) {
        audio.currentTime = (Number(seek.value) / 1000) * audio.duration;
      }
      seeking = false;
    });

    speed.addEventListener('change', function () {
      audio.playbackRate = Number(speed.value);
    });

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/audio-player';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab audio-player] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-src(required)The URL of the audio file (MP3, OGG, or WAV) the player should load.
data-hl-title"Audio track"The title shown above the player controls.
data-hl-artist(none)Optional artist or speaker name shown under the title.
data-hl-accent"#f97316"Color of the play button, seek bar, and focus outlines.

Frequently asked questions

How do I install the Audio Player?

Paste the script tag where you want the player to appear on your page and set data-hl-src to the URL of your audio file. The player renders inline, right where the script tag sits.

Can I change the title, artist, and colors?

Yes — set data-hl-title and data-hl-artist on the script tag for the track info, and use data-hl-accent to change the play button and seek bar color to match your brand.

Which audio formats are supported?

The player uses the browser's built-in audio element, so it plays anything modern browsers support — MP3 works everywhere, and OGG or WAV work in most browsers. For maximum compatibility, use MP3.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JavaScript file with no dependencies. Paste the snippet into any platform that lets you add custom scripts or edit your theme, including WordPress, Shopify, Squarespace, Wix, and plain HTML sites.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch