Free Podcast Player Widget
A podcast episode list with a built-in audio player.
A compact podcast player that renders your episode list from a JSON attribute and plays episodes right on the page, with a play/pause button, seek bar, and auto-advance to the next episode. Great for podcasters, creators, and blogs that want a listen-on-site option without a hosted embed. Paste one script tag where you want it to appear and it works.
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.
<script src="https://hayneslab.dev/widgets/podcast-player.js" data-hl-config='[{"title":"Ep. 12 — Shipping a Client Portal in a Weekend","date":"Jul 28, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3","duration":"6:12"},{"title":"Ep. 11 — Pricing Creative Work Without Guessing","date":"Jul 21, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3","duration":"7:04"},{"title":"Ep. 10 — What Small Businesses Get Wrong About SEO","date":"Jul 14, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3","duration":"5:44"}]' data-hl-title="The Build Log" async></script>
<!-- Free Podcast Player widget by haynes lab — https://hayneslab.dev/tools/widgets/podcast-player --><!-- Free Podcast Player widget by haynes lab — https://hayneslab.dev/tools/widgets/podcast-player -->
<script data-hl-config='[{"title":"Ep. 12 — Shipping a Client Portal in a Weekend","date":"Jul 28, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3","duration":"6:12"},{"title":"Ep. 11 — Pricing Creative Work Without Guessing","date":"Jul 21, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3","duration":"7:04"},{"title":"Ep. 10 — What Small Businesses Get Wrong About SEO","date":"Jul 14, 2026","src":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3","duration":"5:44"}]' data-hl-title="The Build Log">
/*!
* haynes lab — Podcast Player widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/podcast-player
*/
(function () {
'use strict';
var PREFIX = 'hlw-podcast-player';
var STYLE_ID = 'hlw-podcast-player-styles';
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/podcast-player.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { position: relative; max-width: 560px; margin: 0 auto; padding: 24px 24px 28px;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 15px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
' border: 1px solid #e5e7eb; border-radius: 12px; }' +
'.' + PREFIX + '__header { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 16px; }' +
'.' + PREFIX + '__title { margin: 0; font-size: 18px; font-weight: 700; color: #111827; }' +
'.' + PREFIX + '__count { font-size: 12px; color: #9ca3af; white-space: nowrap; }' +
'.' + PREFIX + '__player { display: flex; align-items: center; gap: 14px; padding: 12px 14px;' +
' background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; margin-bottom: 16px; }' +
'.' + 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:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
'.' + PREFIX + '__play svg { display: block; }' +
'.' + PREFIX + '__now { flex: 1 1 auto; min-width: 0; }' +
'.' + PREFIX + '__now-title { font-weight: 600; font-size: 14px; color: #111827;' +
' white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }' +
'.' + PREFIX + '__progress { height: 6px; margin-top: 8px; background: #e5e7eb; border-radius: 999px;' +
' cursor: pointer; position: relative; }' +
'.' + PREFIX + '__progress-fill { position: absolute; inset: 0 auto 0 0; width: 0%;' +
' background: var(--hlw-accent); border-radius: 999px; }' +
'.' + PREFIX + '__time { display: flex; justify-content: space-between; margin-top: 5px;' +
' font-size: 11px; color: #9ca3af; font-variant-numeric: tabular-nums; }' +
'.' + PREFIX + '__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }' +
'.' + PREFIX + '__episode { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;' +
' padding: 10px 12px; border: 1px solid transparent; border-radius: 8px; background: none;' +
' font: inherit; color: inherit; cursor: pointer; }' +
'.' + PREFIX + '__episode:hover { background: #f9fafb; }' +
'.' + PREFIX + '__episode:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
'.' + PREFIX + '__episode[aria-current="true"] { background: #f9fafb; border-color: #e5e7eb; }' +
'.' + PREFIX + '__num { flex: 0 0 auto; width: 24px; font-size: 12px; color: #9ca3af;' +
' font-variant-numeric: tabular-nums; text-align: right; }' +
'.' + PREFIX + '__episode[aria-current="true"] .' + PREFIX + '__num { color: var(--hlw-accent); font-weight: 700; }' +
'.' + PREFIX + '__meta { flex: 1 1 auto; min-width: 0; }' +
'.' + PREFIX + '__ep-title { display: block; font-weight: 600; font-size: 14px; color: #111827;' +
' white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }' +
'.' + PREFIX + '__ep-date { display: block; font-size: 12px; color: #6b7280; margin-top: 1px; }' +
'.' + PREFIX + '__duration { flex: 0 0 auto; font-size: 12px; color: #9ca3af; font-variant-numeric: tabular-nums; }' +
'.' + 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; }' +
'@media (max-width: 480px) { .' + PREFIX + ' { padding: 18px 16px 26px; }' +
' .' + PREFIX + '__duration { display: none; } }';
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
function parseEpisodes(script) {
var raw = script.getAttribute('data-hl-config');
if (!raw) {
console.warn('[haynes lab podcast-player] Missing required data-hl-config attribute (JSON array of {title, date, src, duration?}). Nothing rendered.');
return null;
}
var items;
try {
items = JSON.parse(raw);
} catch (e) {
console.warn('[haynes lab podcast-player] data-hl-config is not valid JSON. Nothing rendered.', e);
return null;
}
if (!Array.isArray(items)) {
console.warn('[haynes lab podcast-player] data-hl-config must be a JSON array. Nothing rendered.');
return null;
}
var valid = items.filter(function (ep) {
return ep && typeof ep.title === 'string' && ep.title && typeof ep.src === 'string' && ep.src;
});
if (!valid.length) {
console.warn('[haynes lab podcast-player] data-hl-config has no valid episodes (each needs at least "title" and "src"). Nothing rendered.');
return null;
}
return valid;
}
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text != null) node.textContent = text;
return node;
}
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', '18');
svg.setAttribute('height', '18');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
if (kind === 'play') {
var p = document.createElementNS(svgNS, 'path');
p.setAttribute('d', 'M8 5v14l11-7z');
p.setAttribute('fill', 'currentColor');
svg.appendChild(p);
} else {
var r1 = document.createElementNS(svgNS, 'rect');
r1.setAttribute('x', '6'); r1.setAttribute('y', '5');
r1.setAttribute('width', '4'); r1.setAttribute('height', '14');
r1.setAttribute('fill', 'currentColor');
var r2 = document.createElementNS(svgNS, 'rect');
r2.setAttribute('x', '14'); r2.setAttribute('y', '5');
r2.setAttribute('width', '4'); r2.setAttribute('height', '14');
r2.setAttribute('fill', 'currentColor');
svg.appendChild(r1);
svg.appendChild(r2);
}
return svg;
}
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 fmtTime(sec) {
if (!isFinite(sec) || sec < 0) return '0:00';
var m = Math.floor(sec / 60);
var s = Math.floor(sec % 60);
return m + ':' + (s < 10 ? '0' : '') + s;
}
function build(script) {
var items = parseEpisodes(script);
if (!items) return;
var title = script.getAttribute('data-hl-title') || 'Podcast';
var accent = script.getAttribute('data-hl-accent') || '#f97316';
injectStyles();
var root = el('div', PREFIX);
root.style.setProperty('--hlw-accent', accent);
var header = el('div', PREFIX + '__header');
header.appendChild(el('h3', PREFIX + '__title', title));
header.appendChild(el('span', PREFIX + '__count',
items.length + (items.length === 1 ? ' episode' : ' episodes')));
root.appendChild(header);
var audio = document.createElement('audio');
audio.preload = 'metadata';
var player = el('div', PREFIX + '__player');
var playBtn = el('button', PREFIX + '__play');
playBtn.type = 'button';
playBtn.setAttribute('aria-label', 'Play');
playBtn.appendChild(iconSvg('play'));
var now = el('div', PREFIX + '__now');
var nowTitle = el('div', PREFIX + '__now-title', 'Select an episode');
var progress = el('div', PREFIX + '__progress');
progress.setAttribute('role', 'progressbar');
progress.setAttribute('aria-label', 'Playback progress');
var fill = el('div', PREFIX + '__progress-fill');
progress.appendChild(fill);
var time = el('div', PREFIX + '__time');
var curTime = el('span', null, '0:00');
var totTime = el('span', null, '0:00');
time.appendChild(curTime);
time.appendChild(totTime);
now.appendChild(nowTitle);
now.appendChild(progress);
now.appendChild(time);
player.appendChild(playBtn);
player.appendChild(now);
root.appendChild(player);
var list = el('ol', PREFIX + '__list');
root.appendChild(list);
var current = -1;
function setPlaying(playing) {
playBtn.textContent = '';
playBtn.appendChild(iconSvg(playing ? 'pause' : 'play'));
playBtn.setAttribute('aria-label', playing ? 'Pause' : 'Play');
}
function load(i, autoplay) {
if (i === current) {
if (autoplay && audio.paused) audio.play();
return;
}
current = i;
audio.src = items[i].src;
nowTitle.textContent = items[i].title;
Array.prototype.forEach.call(list.children, function (li, liIdx) {
li.firstChild.setAttribute('aria-current', liIdx === i ? 'true' : 'false');
});
if (autoplay) {
var p = audio.play();
if (p && p.catch) p.catch(function () { setPlaying(false); });
}
}
items.forEach(function (ep, i) {
var li = document.createElement('li');
var btn = el('button', PREFIX + '__episode');
btn.type = 'button';
btn.setAttribute('aria-current', 'false');
btn.setAttribute('aria-label', 'Play episode: ' + ep.title);
btn.appendChild(el('span', PREFIX + '__num', String(i + 1)));
var meta = el('span', PREFIX + '__meta');
meta.appendChild(el('span', PREFIX + '__ep-title', ep.title));
if (ep.date) meta.appendChild(el('span', PREFIX + '__ep-date', String(ep.date)));
btn.appendChild(meta);
if (ep.duration) btn.appendChild(el('span', PREFIX + '__duration', String(ep.duration)));
btn.addEventListener('click', function () {
if (i === current && !audio.paused) {
audio.pause();
} else {
load(i, true);
}
});
li.appendChild(btn);
list.appendChild(li);
});
playBtn.addEventListener('click', function () {
if (current === -1) { load(0, true); return; }
if (audio.paused) audio.play(); else audio.pause();
});
progress.addEventListener('click', function (e) {
if (!isFinite(audio.duration) || !audio.duration) return;
var rect = progress.getBoundingClientRect();
var frac = (e.clientX - rect.left) / rect.width;
audio.currentTime = Math.max(0, Math.min(1, frac)) * audio.duration;
});
audio.addEventListener('play', function () { setPlaying(true); });
audio.addEventListener('pause', function () { setPlaying(false); });
audio.addEventListener('loadedmetadata', function () {
totTime.textContent = fmtTime(audio.duration);
});
audio.addEventListener('timeupdate', function () {
curTime.textContent = fmtTime(audio.currentTime);
if (isFinite(audio.duration) && audio.duration) {
fill.style.width = (audio.currentTime / audio.duration * 100) + '%';
}
});
audio.addEventListener('ended', function () {
if (current + 1 < items.length) load(current + 1, true);
});
load(0, false);
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/podcast-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 podcast-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.
| Attribute | Default | Description |
|---|---|---|
data-hl-config | (required) | Episode list as [{"title":"...","date":"...","src":"...","duration":"..."}] where src is the audio file URL and duration is optional display text like "24:15". |
data-hl-title | "Podcast" | Heading shown above the episode list. |
data-hl-accent | "#f97316" | Any CSS color used for the play button, progress bar, and active episode. |
Frequently asked questions
How do I install the podcast player?
Paste the embed snippet where you want the player to appear and put your episodes in the data-hl-config attribute as [{"title":"...","date":"...","src":"..."}] with src pointing at each audio file URL. The widget renders right where the script tag sits, and the first episode loads ready to play.
Can I customize the title and colors?
Yes. Use data-hl-title for the heading text and data-hl-accent for the play button, progress bar, and active-episode highlight. Everything else is styled neutrally to fit any light website.
Does it work on WordPress, Shopify, or Squarespace?
Yes. The player is a single dependency-free vanilla JS file with its own scoped styles and a standard HTML5 audio element. Drop the script tag into a Custom HTML block on any platform, or straight into your page HTML.
More free widgets
Audio Player
A clean, custom single-track audio player with seek, time, and speed controls.
View toolContent & MediaVideo Popup
Clickable thumbnail that opens a YouTube or Vimeo video in a modal.
View toolContent & MediaImage Carousel
Responsive image slideshow with arrows, dots, autoplay, and swipe support.
View toolNeed 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