1// ElevenLabs sticky audio player â externalized. Guard DOMContentLoaded: 2// init queries #ppl-audio-bar (rendered in wp_footer priority 35) â wait for DOM ready. 3(function () { 4 function pplAudioInit() { 5 (function () { 6 var LS_KEY = 'ppl_audio_session'; 7 var bar = document.getElementById('ppl-audio-bar'); 8 if (!bar) return; 9 10 var seek = bar.querySelector('.ppl-audio-bar__seek'); 11 var cover = bar.querySelector('.ppl-audio-bar__cover'); 12 var trackEl = bar.querySelector('.ppl-audio-bar__track'); 13 var epEl = bar.querySelector('.ppl-audio-bar__episode'); 14 var curEl = bar.querySelector('.ppl-audio-bar__cur'); 15 var totEl = bar.querySelector('.ppl-audio-bar__tot'); 16 var playBtn = bar.querySelector('.ppl-audio-bar__play'); 17 var prevBtn = bar.querySelector('.ppl-audio-bar__prev'); 18 var nextBtn = bar.querySelector('.ppl-audio-bar__next'); 19 var closeBtn= bar.querySelector('.ppl-audio-bar__close'); 20 var speedSel= bar.querySelector('.ppl-audio-bar__speed'); 21 22 var SPEEDS = [1, 1.25, 1.5, 1.75, 2]; 23 var audio = new Audio(); 24 audio.preload = 'none'; 25 26 function applyRate() { 27 var r = (session && session.rate) ? session.rate : 1; 28 audio.playbackRate = r; 29 if (speedSel) speedSel.value = String(r); 30 } 31 var session = null; // { episode: {id,title,cover,tracks:[{title,url}]}, idx, pos } 32 var saveTick = 0; 33 34 function fmt(s) { 35 if (!isFinite(s)) return 'â:ââ'; 36 s = Math.max(0, Math.floor(s)); 37 return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); 38 } 39 function load() { 40 try { return JSON.parse(localStorage.getItem(LS_KEY) || 'null'); } catch (e) { return null; } 41 } 42 function save() { 43 if (!session) return; 44 session.pos = audio.currentTime || session.pos || 0; 45 session.paused = audio.paused; 46 if (isFinite(audio.duration)) session.dur = audio.duration; 47 try { localStorage.setItem(LS_KEY, JSON.stringify(session)); } catch (e) {} 48 } 49 function clear() { 50 try { localStorage.removeItem(LS_KEY); } catch (e) {} 51 } 52 53 function render() { 54 var tr = session.episode.tracks[session.idx]; 55 trackEl.textContent = tr.title; 56 epEl.textContent = session.episode.title; 57 if (session.episode.cover) { cover.src = session.episode.cover; cover.hidden = false; } 58 else cover.hidden = true; 59 prevBtn.disabled = session.idx <= 0; 60 nextBtn.disabled = session.idx >= session.episode.tracks.length - 1; 61 bar.hidden = false; 62 document.body.classList.add('ppl-audio-bar-open'); 63 } 64 65 function setTrack(idx, pos) { 66 var p = pos != null ? pos : 0; 67 session.idx = idx; 68 session.pos = p; 69 if (pos == null) delete session.dur; 70 audio.src = session.episode.tracks[idx].url; 71 applyRate(); 72 if (p) { 73 audio.addEventListener('loadedmetadata', function () { audio.currentTime = p; }, { once: true }); 74 } 75 curEl.textContent = fmt(p); 76 var knownDur = (p && session.dur) ? session.dur : NaN; 77 totEl.textContent = fmt(knownDur); 78 var pct = (p && isFinite(knownDur)) ? Math.round((p / knownDur) * 1000) : 0; 79 seek.value = pct; 80 seek.style.setProperty('--pp', (pct / 10) + '%'); 81 render(); 82 save(); 83 document.dispatchEvent(new CustomEvent('pplaudio:track', { 84 detail: { episodeId: session.episode.id, idx: idx } 85 })); 86 } 87 88 function show(sess, autoplay) { 89 session = sess; 90 if (!session.rate || SPEEDS.indexOf(session.rate) === -1) session.rate = 1; 91 setTrack(sess.idx || 0, sess.pos || 0); 92 if (autoplay) audio.play().catch(function () { /* blocked â wait for click */ }); 93 } 94 95 /* ââ Public API âââââââââââââââââââââââââââââââââââââââââââââââ */ 96 window.PPLAudio = {
97 playEpisode: function (episode, idx) { 98 show({ episode: episode, idx: idx || 0, pos: 0 }, true); 99 }, 100 toggleTrack: function (episodeId, idx) { 101 if (session && session.episode.id === episodeId && session.idx === idx) { 102 if (audio.paused) audio.play(); else audio.pause(); 103 return; 104 } 105 return false; 106 }, 107 pause: function () { if (!audio.paused) audio.pause(); }, 108 state: function () { 109 return session ? { episodeId: session.episode.id, idx: session.idx, playing: !audio.paused } : null; 110 } 111 }; 112 113 window.addEventListener('message', function (e) { 114 if (e.origin !== window.location.origin) return; 115 if (e.data && e.data.type === 'ppl:viewer-audio-play') audio.pause(); 116 }); 117 118 /* ââ Controls ââââââââââââââââââââââââââââââââââââââââââââââââââ */ 119 playBtn.addEventListener('click', function () { 120 if (audio.paused) audio.play(); else audio.pause(); 121 }); 122 prevBtn.addEventListener('click', function () { 123 if (session && session.idx > 0) { setTrack(session.idx - 1); audio.play(); } 124 }); 125 nextBtn.addEventListener('click', function () { 126 if (session && session.idx < session.episode.tracks.length - 1) { setTrack(session.idx + 1); audio.play(); } 127 }); 128 closeBtn.addEventListener('click', function () { 129 audio.pause(); 130 audio.removeAttribute('src'); 131 bar.hidden = true; 132 document.body.classList.remove('ppl-audio-bar-open'); 133 session = null; 134 clear(); 135 document.dispatchEvent(new CustomEvent('pplaudio:stop')); 136 }); 137 seek.addEventListener('input', function () { 138 if (isFinite(audio.duration)) audio.currentTime = (seek.value / 1000) * audio.duration; 139 }); 140 if (speedSel) speedSel.addEventListener('change', function () { 141 if (!session) return; 142 var r = parseFloat(speedSel.value) || 1; 143 session.rate = SPEEDS.indexOf(r) !== -1 ? r : 1; 144 applyRate(); 145 save(); 146 }); 147 148 /* ââ Audio events âââââââââââââââââââââââââââââââââââââââââââââââ */ 149 audio.addEventListener('play', function () { 150 bar.classList.add('is-playing'); 151 save(); 152 document.dispatchEvent(new CustomEvent('pplaudio:play', { detail: window.PPLAudio.state() })); 153 }); 154 audio.addEventListener('pause', function () { 155 bar.classList.remove('is-playing'); 156 save(); 157 document.dispatchEvent(new CustomEvent('pplaudio:pause', { detail: window.PPLAudio.state() })); 158 }); 159 audio.addEventListener('loadedmetadata', function () { 160 totEl.textContent = fmt(audio.duration); 161 if (isFinite(audio.duration)) session.dur = audio.duration; 162 applyRate(); // some browsers reset playbackRate on source load 163 }); 164 audio.addEventListener('timeupdate', function () { 165 if (!isFinite(audio.duration)) return; 166 var p = Math.round((audio.currentTime / audio.duration) * 1000); 167 seek.value = p; 168 seek.style.setProperty('--pp', (p / 10) + '%'); 169 curEl.textContent = fmt(audio.currentTime); 170 if (Date.now() - saveTick > 3000) { saveTick = Date.now(); save(); } 171 }); 172 audio.addEventListener('ended', function () { 173 if (session && session.idx < session.episode.tracks.length - 1) { 174 setTrack(session.idx + 1); 175 audio.play(); 176 } else { 177 session.pos = 0; 178 save(); 179 } 180 }); 181 window.addEventListener('pagehide', save); 182 183 /* ââ Session resume on page load âââââââââââââââââââ */ 184 var prev = load(); 185 if (prev && prev.episode && prev.episode.tracks && prev.episode.tracks[prev.idx]) { 186 // Auto-resume only if playback was active when leaving page 187 // (paused by user â respect pause). If browser blocks auto
187play, bar shows paused at saved position. 188 show(prev, !prev.paused); 189 } 190 })(); 191 } 192 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', pplAudioInit); 193 else pplAudioInit(); 194})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.