| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
/* ── Helpers ──────────────────────────────────────────────────────────── */ |
| 5 |
function formatTime(secs) { |
| 6 |
if (isNaN(secs) || secs < 0) return '0:00'; |
| 7 |
var m = Math.floor(secs / 60); |
| 8 |
var s = Math.floor(secs % 60); |
| 9 |
return m + ':' + (s < 10 ? '0' : '') + s; |
| 10 |
} |
| 11 |
|
| 12 |
/* ── Build SVG element ────────────────────────────────────────────────── */ |
| 13 |
function svgIcon(d, size) { |
| 14 |
size = size || 22; |
| 15 |
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
| 16 |
svg.setAttribute('viewBox', '0 0 24 24'); |
| 17 |
svg.setAttribute('fill', 'currentColor'); |
| 18 |
svg.setAttribute('width', size); |
| 19 |
svg.setAttribute('height', size); |
| 20 |
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| 21 |
path.setAttribute('d', d); |
| 22 |
svg.appendChild(path); |
| 23 |
return svg; |
| 24 |
} |
| 25 |
|
| 26 |
var PLAY_PATH = 'M8 5v14l11-7z'; |
| 27 |
var PAUSE_PATH = 'M6 19h4V5H6v14zm8-14v14h4V5h-4z'; |
| 28 |
|
| 29 |
/* ── Init one player ──────────────────────────────────────────────────── */ |
| 30 |
function initAudioPlayer(wrapper) { |
| 31 |
var playerEl = wrapper.querySelector('.bkbg-ap-player'); |
| 32 |
if (!playerEl) return; |
| 33 |
|
| 34 |
var audioUrl = playerEl.dataset.audioUrl; |
| 35 |
var audioType = playerEl.dataset.audioType || 'audio/mpeg'; |
| 36 |
if (!audioUrl) return; |
| 37 |
|
| 38 |
var autoplay = playerEl.dataset.autoplay === '1'; |
| 39 |
var loop = playerEl.dataset.loop === '1'; |
| 40 |
var preload = playerEl.dataset.preload || 'metadata'; |
| 41 |
var showVolume = playerEl.dataset.showVolume !== '0'; |
| 42 |
var showSpeed = playerEl.dataset.showSpeed !== '0'; |
| 43 |
|
| 44 |
/* Audio element */ |
| 45 |
var audio = new Audio(); |
| 46 |
audio.src = audioUrl; |
| 47 |
audio.preload = preload; |
| 48 |
audio.loop = loop; |
| 49 |
if (autoplay) { audio.autoplay = true; } |
| 50 |
|
| 51 |
/* DOM refs */ |
| 52 |
var playBtn = playerEl.querySelector('.bkbg-ap-play-btn'); |
| 53 |
var progressTrack = playerEl.querySelector('.bkbg-ap-progress-track'); |
| 54 |
var progressFill = playerEl.querySelector('.bkbg-ap-progress-fill'); |
| 55 |
var progressThumb = playerEl.querySelector('.bkbg-ap-progress-thumb'); |
| 56 |
var currentEl = playerEl.querySelector('.bkbg-ap-current'); |
| 57 |
var durationEl = playerEl.querySelector('.bkbg-ap-duration'); |
| 58 |
var volSlider = playerEl.querySelector('.bkbg-ap-vol-slider'); |
| 59 |
var speedBtn = playerEl.querySelector('.bkbg-ap-speed-btn'); |
| 60 |
|
| 61 |
/* ── Build play button icons ────────────────────────────────────── */ |
| 62 |
if (playBtn) { |
| 63 |
playBtn.innerHTML = ''; |
| 64 |
var iconPlay = svgIcon(PLAY_PATH, 22); |
| 65 |
iconPlay.classList.add('bkbg-ap-icon-play'); |
| 66 |
var iconPause = svgIcon(PAUSE_PATH, 22); |
| 67 |
iconPause.classList.add('bkbg-ap-icon-pause'); |
| 68 |
iconPause.style.display = 'none'; |
| 69 |
playBtn.appendChild(iconPlay); |
| 70 |
playBtn.appendChild(iconPause); |
| 71 |
} |
| 72 |
|
| 73 |
/* ── Play/Pause ─────────────────────────────────────────────────── */ |
| 74 |
function updatePlayState(playing) { |
| 75 |
wrapper.classList.toggle('bkbg-ap-playing', playing); |
| 76 |
if (!playBtn) return; |
| 77 |
var ip = playBtn.querySelector('.bkbg-ap-icon-play'); |
| 78 |
var ipa = playBtn.querySelector('.bkbg-ap-icon-pause'); |
| 79 |
if (ip) ip.style.display = playing ? 'none' : 'block'; |
| 80 |
if (ipa) ipa.style.display = playing ? 'block' : 'none'; |
| 81 |
playBtn.setAttribute('aria-label', playing ? 'Pause' : 'Play'); |
| 82 |
} |
| 83 |
|
| 84 |
if (playBtn) { |
| 85 |
playBtn.addEventListener('click', function () { |
| 86 |
if (audio.paused) { |
| 87 |
audio.play().catch(function () {}); |
| 88 |
} else { |
| 89 |
audio.pause(); |
| 90 |
} |
| 91 |
}); |
| 92 |
} |
| 93 |
|
| 94 |
audio.addEventListener('play', function () { updatePlayState(true); }); |
| 95 |
audio.addEventListener('pause', function () { updatePlayState(false); }); |
| 96 |
audio.addEventListener('ended', function () { updatePlayState(false); }); |
| 97 |
|
| 98 |
/* ── Progress ───────────────────────────────────────────────────── */ |
| 99 |
audio.addEventListener('loadedmetadata', function () { |
| 100 |
if (durationEl) durationEl.textContent = formatTime(audio.duration); |
| 101 |
}); |
| 102 |
|
| 103 |
audio.addEventListener('timeupdate', function () { |
| 104 |
if (!audio.duration) return; |
| 105 |
var pct = (audio.currentTime / audio.duration) * 100; |
| 106 |
if (progressFill) progressFill.style.width = pct + '%'; |
| 107 |
if (progressThumb) progressThumb.style.left = pct + '%'; |
| 108 |
if (currentEl) currentEl.textContent = formatTime(audio.currentTime); |
| 109 |
}); |
| 110 |
|
| 111 |
/* Seek by clicking the track */ |
| 112 |
function seek(e) { |
| 113 |
if (!progressTrack || !audio.duration) return; |
| 114 |
var rect = progressTrack.getBoundingClientRect(); |
| 115 |
var x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left; |
| 116 |
var pct = Math.min(1, Math.max(0, x / rect.width)); |
| 117 |
audio.currentTime = pct * audio.duration; |
| 118 |
} |
| 119 |
|
| 120 |
if (progressTrack) { |
| 121 |
var seeking = false; |
| 122 |
progressTrack.addEventListener('mousedown', function (e) { |
| 123 |
seeking = true; |
| 124 |
seek(e); |
| 125 |
}); |
| 126 |
progressTrack.addEventListener('touchstart', function (e) { |
| 127 |
seeking = true; |
| 128 |
seek(e); |
| 129 |
}, { passive: true }); |
| 130 |
document.addEventListener('mousemove', function (e) { if (seeking) seek(e); }); |
| 131 |
document.addEventListener('touchmove', function (e) { if (seeking) seek(e); }, { passive: true }); |
| 132 |
document.addEventListener('mouseup', function () { seeking = false; }); |
| 133 |
document.addEventListener('touchend', function () { seeking = false; }); |
| 134 |
} |
| 135 |
|
| 136 |
/* ── Volume ─────────────────────────────────────────────────────── */ |
| 137 |
if (volSlider && showVolume) { |
| 138 |
volSlider.value = audio.volume; |
| 139 |
volSlider.addEventListener('input', function () { |
| 140 |
audio.volume = parseFloat(volSlider.value); |
| 141 |
}); |
| 142 |
} |
| 143 |
|
| 144 |
/* ── Playback speed ─────────────────────────────────────────────── */ |
| 145 |
var SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2]; |
| 146 |
var speedIdx = 2; // default 1× |
| 147 |
|
| 148 |
if (speedBtn && showSpeed) { |
| 149 |
speedBtn.addEventListener('click', function () { |
| 150 |
speedIdx = (speedIdx + 1) % SPEEDS.length; |
| 151 |
audio.playbackRate = SPEEDS[speedIdx]; |
| 152 |
speedBtn.textContent = SPEEDS[speedIdx] + '×'; |
| 153 |
}); |
| 154 |
} |
| 155 |
} |
| 156 |
|
| 157 |
/* ── Init all players on page ─────────────────────────────────────────── */ |
| 158 |
function initAll() { |
| 159 |
var wrappers = document.querySelectorAll('.bkbg-ap-wrapper'); |
| 160 |
wrappers.forEach(initAudioPlayer); |
| 161 |
} |
| 162 |
|
| 163 |
if (document.readyState === 'loading') { |
| 164 |
document.addEventListener('DOMContentLoaded', initAll); |
| 165 |
} else { |
| 166 |
initAll(); |
| 167 |
} |
| 168 |
})(); |
| 169 |
|