1/* ========================================================= 2 WP Audio Player Pro â player.js v2.0 3 Design "barre inline" â correspond à l'image de référence 4 ========================================================= 5 Autoplay fix : 6 - preload="auto" forcé â audio chargé immédiatement 7 - Tentative play() dès 'canplay' (pas d'attente load) 8 - Si NotAllowedError â muted play + overlay unmute 9 - Le slider volume et le bouton mute NE sont PAS altérés 10 lors du fallback (volume affiché ouvert par défaut) 11 ========================================================= */ 12(function () { 13 'use strict'; 14 15 const cfg = window.wappCfg || {}; 16 17 /* ââ Utilitaires ââââââââââââââââââââââââââââââââââââââââ */ 18 function fmt(sec) { 19 sec = Math.floor(sec || 0); 20 return Math.floor(sec / 60) + ':' + ('0' + (sec % 60)).slice(-2); 21 } 22 23 function ajax(data) { 24 if (!cfg.ajax) return; 25 fetch(cfg.ajax, { 26 method: 'POST', 27 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 28 body: new URLSearchParams({ nonce: cfg.nonce || '', ...data }), 29 }).catch(() => {}); 30 } 31 32 /* ââ Menu de partage (réseaux sociaux) âââââââââââââââââââ */ 33 const SHARE_NETWORKS = [ 34 { 35 key: 'facebook', 36 label: 'shareFacebook', 37 icon: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95z"/></svg>', 38 url: (u) => 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(u), 39 }, 40 { 41 key: 'twitter', 42 label: 'shareTwitter', 43 icon: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.26 10.99h-6.466l-5.06-6.62-5.79 6.62H1.96l7.73-8.835L1.5 2.25h6.59l4.583 6.06zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>', 44 url: (u, t) => 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(u) + '&text=' + encodeURIComponent(t || ''), 45 }, 46 { 47 key: 'whatsapp', 48 label: 'shareWhatsapp', 49 icon: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.149-.149.347-.388.521-.581.173-.198.231-.347.347-.581.116-.233.058-.43-.034-.581-.084-.149-.624-1.498-.866-2.05-.221-.49-.444-.43-.61-.43-.165 0-.347-.025-.521-.025-.173 0-.456.067-.69.337-.232.27-.892.875-.892 2.117 0 1.243.916 2.448 1.043 2.617.13.167 1.78 2.72 4.31 3.704 2.531.984 2.531.658 2.99.617.456-.042 1.478-.605 1.685-1.19.207-.582.207-1.08.145-1.189-.058-.108-.231-.173-.521-.322zM12.025 22h-.004a9.94 9.94 0 01-5.07-1.388l-.364-.217-3.762.987 1.004-3.667-.236-.378A9.928 9.928 0 012.025 12C2.025 6.486 6.512 2 12.025 2c2.652 0 5.144 1.034 7.018 2.91A9.91 9.91 0 0122 12.014C22 17.53 17.512 22 12.025 22z"/></svg>', 50 url: (u, t) => 'https://wa.me/?text=' + encodeURIComponent((t ? t + ' - ' : '') + u), 51 }, 52 { 53 key: 'telegram', 54 label: 'shareTelegram', 55 icon: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21.946 4.616a1.5 1.5 0 0 0-1.527-.27L2.6 11.077a1.397 1.397 0 0 0 .073 2.616l4.498 1.49 1.722 5.55a1.04 1.04 0 0 0 1.79.36l2.52-2.79 4.55 3.36a1.39 1.39 0 0 0 2.22-.85l3.18-14.6a1.5 1.5 0 0 0-.207-1.617zM9.1 14.18l-3.93-1.3 12.36-5.06zM9.96 16.4l-.71-2.29 8.34-5.97-7.63 8.26zm1.97 4.5-.99-3.18 1.93-1.7 1.86 4.88z"/></svg>', 56 url: (u, t) => 'https://t.me/share/url?url=' + encodeURIComponent(u) + '&text=' + encodeURIComponent(t || ''), 57 }, 58 { 59 key: 'email', 60 label: 'shareEmail', 61 icon: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z"/></svg>', 62 url: (u, t) => 'mailto:?subject=' + encodeURIComponent(t || '') + '&body=' + encodeURIComponent(u), 63 }, 64 ]; 65 66 function closeShareMenu() { 67 const existing = document.querySelector('.wapp-share-menu'); 68 if (existing) { 69 const ownerBtn = existing._ownerBtn; 70 existing.remove(); 71 if (ownerBtn) ownerBtn.setAttribute('aria-expanded', 'false'); 72 } 73 } 74 75 function buildShareMenu(url, title) { 76 const menu = document.createElement('div'); 77 menu.className = 'wapp-share-menu'; 78 79 SHARE_NETWORKS.forEach(net => { 80 const a = document.createElement('a'); 81 a.className = 'wapp-share-item wapp-share-' + net.key; 82 a.href = net.url(url, title); 83 a.target = '_blank'; 84 a.rel = 'noopener noreferrer'; 85 a.innerHTML = net.icon + '<span>
85' + ((cfg.i18n && cfg.i18n[net.label]) || net.key) + '</span>'; 86 a.addEventListener('click', () => closeShareMenu()); 87 menu.appendChild(a); 88 }); 89 90 return menu; 91 } 92 93 function toggleShareMenu(btn, url, title) { 94 const existing = document.querySelector('.wapp-share-menu'); 95 if (existing) { 96 const wasOwner = existing._ownerBtn === btn; 97 closeShareMenu(); 98 if (wasOwner) return; 99 } 100 101 const menu = buildShareMenu(url, title); 102 menu._ownerBtn = btn; 103 document.body.appendChild(menu); 104 105 const r = btn.getBoundingClientRect(); 106 const menuW = menu.offsetWidth || 170; 107 let left = r.right - menuW; 108 if (left < 8) left = 8; 109 if (left + menuW > window.innerWidth - 8) left = window.innerWidth - menuW - 8; 110 let top = r.top - menu.offsetHeight - 6; 111 if (top < 8) top = r.bottom + 6; // si pas de place au-dessus, ouvrir vers le bas 112 113 menu.style.left = left + 'px'; 114 menu.style.top = top + 'px'; 115 116 requestAnimationFrame(() => menu.classList.add('open')); 117 btn.setAttribute('aria-expanded', 'true'); 118 } 119 120 // Fermer le menu de partage au clic extérieur, à l'échappement, scroll ou resize 121 document.addEventListener('click', e => { 122 if (!e.target.closest('.wapp-share-menu, .wapp-bar-share, .wapp-pl-share-btn')) closeShareMenu(); 123 }); 124 document.addEventListener('keydown', e => { 125 if (e.key === 'Escape') closeShareMenu(); 126 }); 127 window.addEventListener('scroll', closeShareMenu, true); 128 window.addEventListener('resize', closeShareMenu); 129 130 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 131 AUTOPLAY ENGINE v2.0 132 ââââââââââââââââââââââââââââââââââââââââââââââââââââ 133 1. Force preload="auto" pour déclencher le chargement 134 2. Ãcoute 'canplay' â tente play() non-muté 135 3. NotAllowedError â bascule muted SANS toucher 136 au slider ni au bouton mute (UI reste "son ouvert") 137 4. Overlay "Cliquez pour activer le son" 138 5. Au clic utilisateur â unmute propre 139 ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 140 function tryAutoplay(audio, wrap, volPct, onSuccess) { 141 /* Garantir que l'audio charge */ 142 if (audio.preload !== 'auto') audio.preload = 'auto'; 143 if (!audio.src && audio.dataset && audio.dataset.url) audio.src = audio.dataset.url; 144 145 function attempt() { 146 audio.muted = false; 147 audio.volume = Math.min(1, (volPct || 80) / 100); 148 149 audio.play() 150 .then(() => { if (onSuccess) onSuccess(); }) 151 .catch(err => { 152 if (err.name !== 'NotAllowedError') return; 153 154 /* Fallback muted â NE pas toucher à l'UI volume */ 155 audio.muted = true; 156 wrap.classList.add('wapp-muted-autoplay'); 157 158 audio.play() 159 .then(() => showUnmuteOverlay(audio, wrap, volPct)) 160 .catch(() => { 161 wrap.classList.remove('wapp-muted-autoplay'); 162 audio.muted = false; 163 audio.volume = Math.min(1, (volPct || 80) / 100); 164 }); 165 }); 166 } 167 168 /* readyState 3 = HAVE_FUTURE_DATA, 4 = HAVE_ENOUGH_DATA */ 169 if (audio.readyState >= 3) { 170 attempt(); 171 } else { 172 audio.addEventListener('canplay', attempt, { once: true }); 173 /* Filet de sécurité : si canplay ne se déclenche pas dans 2s */ 174 setTimeout(() => { if (audio.paused) attempt(); }, 2000); 175 } 176 } 177 178 function showUnmuteOverlay(audio, wrap, volPct) { 179 if (wrap.querySelector('.wapp-unmute-overlay')) return; 180 181 const ov = document.createElement('div'); 182 ov.className = 'wapp-unmute-overlay'; 183 ov.innerHTML = 184 '<div class="wapp-unmute-inner">' + 185 '<span class="wapp-unmute-icon">ð</span>' + 186 '<span class="wapp-unmute-label">' + 187 ((cfg.i18n && cfg.i18n.clickUnmute) || 'Cliquez pour activer le son') + 188 '</span>' + 189 '</div>'; 190 wrap.appendChild(ov); 191 192 function unmute(e) { 193 if (e && e.isTrusted === false) return; 194 audio.muted = false;
195 audio.volume = Math.min(1, (volPct || 80) / 100); 196 197 /* Synchro UI mute button si présent */ 198 const btn = wrap.querySelector('.wapp-bar-mute'); 199 if (btn) btn.classList.remove('muted'); 200 201 wrap.classList.remove('wapp-muted-autoplay'); 202 ov.classList.add('wapp-unmute-fading'); 203 setTimeout(() => ov.remove(), 380); 204 ['click', 'keydown', 'touchend'].forEach(ev => 205 document.removeEventListener(ev, unmute) 206 ); 207 } 208 209 ov.addEventListener('click', unmute); 210 setTimeout(() => { 211 ['click', 'keydown', 'touchend'].forEach(ev => 212 document.addEventListener(ev, unmute, { once: true, passive: true }) 213 ); 214 }, 120); 215 } 216 217 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 218 INIT PLAYER BAR 219 ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 220 function initPlayer(wrap) { 221 const d = wrap.dataset; 222 if (!d.url) return; 223 224 /* Créer l'élément <audio> */ 225 const audio = document.createElement('audio'); 226 audio.preload = 'auto'; /* toujours "auto" pour autoplay fiable */ 227 audio.loop = d.loop === '1'; 228 audio.volume = Math.min(1, Math.max(0, (parseInt(d.volume) || 80) / 100)); 229 audio.src = d.url; 230 231 /* Refs DOM */ 232 const btnPlay = wrap.querySelector('.wapp-bar-play'); 233 const iconPlay = wrap.querySelector('.wapp-icon-play'); 234 const iconPause = wrap.querySelector('.wapp-icon-pause'); 235 const btnMute = wrap.querySelector('.wapp-bar-mute'); 236 const iconVolOn = wrap.querySelector('.wapp-icon-vol-on'); 237 const iconVolOff= wrap.querySelector('.wapp-icon-vol-off'); 238 const waveBars = Array.from(wrap.querySelectorAll('.wapp-bar-wbar')); 239 const waveSeek = wrap.querySelector('.wapp-bar-wave-seek'); 240 const cursor = wrap.querySelector('.wapp-bar-cursor'); 241 const timerEl = wrap.querySelector('.wapp-time-current'); 242 const btnLoop = wrap.querySelector('.wapp-bar-loop'); 243 const btnShare = wrap.querySelector('.wapp-bar-share'); 244 const btnDown = wrap.querySelector('.wapp-bar-download'); 245 const btnSpeed = wrap.querySelector('.wapp-bar-speed'); 246 const speedMenu = wrap.querySelector('.wapp-bar-speed-menu'); 247 248 /* ââ UI play/pause âââââââââââââââââââââââââââââââââ */ 249 function setPlayUI(playing) { 250 if (iconPlay) iconPlay.style.display = playing ? 'none' : ''; 251 if (iconPause) iconPause.style.display = playing ? '' : 'none'; 252 wrap.classList.toggle('wapp-playing', playing); 253 } 254 255 /* ââ UI mute âââââââââââââââââââââââââââââââââââââââ */ 256 function setMuteUI(muted) { 257 if (!btnMute) return; 258 btnMute.classList.toggle('muted', muted); 259 if (iconVolOn) iconVolOn.style.display = muted ? 'none' : ''; 260 if (iconVolOff) iconVolOff.style.display = muted ? '' : 'none'; 261 } 262 /* Initialisation : son ouvert */ 263 setMuteUI(false); 264 265 /* ââ Progression & waveform ââââââââââââââââââââââââ */ 266 function updateProgress() { 267 const pct = audio.duration ? audio.currentTime / audio.duration : 0; 268 269 /* Curseur */ 270 if (cursor) cursor.style.left = (pct * 100) + '%'; 271 272 /* Timer */ 273 if (timerEl) timerEl.textContent = fmt(audio.currentTime); 274 275 /* Waveform coloring */ 276 if (waveBars.length) { 277 const activeIdx = Math.round(pct * (waveBars.length - 1)); 278 waveBars.forEach((b, i) => { 279 b.classList.toggle('played', i < activeIdx); 280 b.classList.toggle('active', i === activeIdx); 281 }); 282 } 283 } 284 285 /* ââ Seek ââââââââââââââââââââââââââââââââââââââââââ */ 286 function seekByEvent(e, el) { 287 const r = el.getBoundingClientRect(); 288 const clientX = e.touches ? e.touches[0].clientX : e.clientX; 289 const pct = Math.max(0, Math.min(1, (clientX - r.left) / r.width)); 290 if (audio.duration) { 291 audio.currentTime = pct * audio.duration; 292 ajax({ action: 'wapp_event', event: 'seek', id: d.id || '' }); 293 } 294 } 295 296 /* ââ Play/pause toggle âââââââââââââââââââââââââââââ */ 297 function togglePlay() { 298 if (audio.paused) { 299 audio.play() 300 .then(() => ajax({ action: 'wapp_event', event: 'play', id: d.id || '' })) 301 .catch(() => {}); 302 } else { 303 audio.pause(); 304 ajax({ action: 'wapp_event', event: 'pause', id: d.id || '' }); 305 } 306 } 307 308 /* ââ Ãvénements audio ââââââââââââââââââââââââââââââ */ 309 audio.addEventListener('play', () => setPlayUI(true)); 310 audio.addEventListener('pause', () => setPlayUI(false)); 311 audio.addEventListener('timeupdate', updateProgress); 312 audio.addEventListener('loadedmetadata', updateProgress); 313 audio.addEventListener('ended', () => { 314 setPlayUI(false); 315 ajax({ action: 'wapp_event', event: 'complete', id: d.id || '' }); 316 }); 317 318 /* ââ Bouton play âââââââââââââââââââââââââââââââââââ */ 319 if (btnPlay) btnPlay.addEventListener('click', togglePlay); 320 321 /* ââ Bouton mute âââââââââââââââââââââââââââââââââââ */ 322 if (btnMute) btnMute.addEventListener('click', () => { 323 audio.muted = !audio.muted; 324 setMuteUI(audio.muted); 325 }); 326 327 /* ââ Seek sur waveform âââââââââââââââââââââââââââââ */ 328 if (waveSeek) { 329 let dragging = false;
330 waveSeek.addEventListener('mousedown', e => { dragging = true; seekByEvent(e, waveSeek); }); 331 document.addEventListener('mousemove', e => { if (dragging) seekByEvent(e, waveSeek); }); 332 document.addEventListener('mouseup', () => { dragging = false; }); 333 waveSeek.addEventListener('touchstart', e => seekByEvent(e, waveSeek), { passive: true }); 334 waveSeek.addEventListener('touchmove', e => seekByEvent(e, waveSeek), { passive: true }); 335 } 336 337 /* ââ Loop ââââââââââââââââââââââââââââââââââââââââââ */ 338 if (btnLoop) btnLoop.addEventListener('click', () => { 339 audio.loop = !audio.loop; 340 btnLoop.classList.toggle('active', audio.loop); 341 }); 342 343 /* ââ Share âââââââââââââââââââââââââââââââââââââââââ */ 344 if (btnShare) btnShare.addEventListener('click', e => { 345 e.preventDefault(); e.stopPropagation(); 346 toggleShareMenu(btnShare, d.url || window.location.href, d.title || document.title); 347 }); 348 349 /* ââ Speed âââââââââââââââââââââââââââââââââââââââââ */ 350 if (btnSpeed && speedMenu) { 351 btnSpeed.addEventListener('click', e => { 352 e.stopPropagation(); 353 speedMenu.classList.toggle('open'); 354 }); 355 speedMenu.querySelectorAll('button[data-speed]').forEach(b => { 356 b.addEventListener('click', () => { 357 audio.playbackRate = parseFloat(b.dataset.speed) || 1; 358 btnSpeed.textContent = b.dataset.speed + 'Ã'; 359 speedMenu.querySelectorAll('button').forEach(x => x.classList.remove('active')); 360 b.classList.add('active'); 361 speedMenu.classList.remove('open'); 362 }); 363 }); 364 document.addEventListener('click', e => { 365 if (btnSpeed && !btnSpeed.contains(e.target)) speedMenu.classList.remove('open'); 366 }); 367 } 368 369 /* ââ Clavier âââââââââââââââââââââââââââââââââââââââ */ 370 if (d.keyboard === '1') { 371 wrap.setAttribute('tabindex', '0'); 372 wrap.addEventListener('keydown', e => { 373 const map = { 374 'Space': () => { e.preventDefault(); togglePlay(); }, 375 'ArrowRight': () => { e.preventDefault(); audio.currentTime = Math.min(audio.duration || 0, audio.currentTime + 5); }, 376 'ArrowLeft': () => { e.preventDefault(); audio.currentTime = Math.max(0, audio.currentTime - 5); }, 377 'ArrowUp': () => { e.preventDefault(); audio.volume = Math.min(1, audio.volume + 0.1); }, 378 'ArrowDown': () => { e.preventDefault(); audio.volume = Math.max(0, audio.volume - 0.1); }, 379 'KeyM': () => { audio.muted = !audio.muted; setMuteUI(audio.muted); }, 380 }; 381 if (map[e.code]) map[e.code](); 382 }); 383 } 384 385 /* ââ Start at ââââââââââââââââââââââââââââââââââââââ */ 386 const startAt = parseInt(d.startAt) || 0; 387 if (startAt > 0) { 388 audio.addEventListener('loadedmetadata', () => { audio.currentTime = startAt; }, { once: true }); 389 } 390 391 /* ââââââââââââââââââââââââââââââââââââââââââââââââââ 392 AUTOPLAY 393 preload="auto" est déjà défini â le navigateur 394 charge le fichier dès que possible. 395 On lance tryAutoplay() : 396 - immédiatement si le doc est prêt 397 - sinon on attend DOMContentLoaded (pas "load" 398 qui est trop tardif sur certains navigateurs) 399 ââââââââââââââââââââââââââââââââââââââââââââââââââ */ 400 if (d.autoplay === '1') { 401 const volPct = parseInt(d.volume) || 80; 402 const doAP = () => tryAutoplay(audio, wrap, volPct, null); 403 404 if (document.readyState !== 'loading') { 405 /* Page déjà interactive â lancer tout de suite */ 406 doAP(); 407 } else { 408 document.addEventListener('DOMContentLoaded', doAP, { once: true }); 409 } 410 } 411 } 412 413 /* ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 414 PLAYLIST (inchangée v1.2) 415 ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 416 function initPlaylist(wrap) { 417 const d = wrap.dataset; 418 const audio = wrap.querySelector('.wapp-pl-audio'); 419 if (!audio) return; 420 421 const trackEls = Array.from(wrap.querySelectorAll('.wapp-pl-track'));
422 let current = -1, looping = false, shuffled = d.shuffle === '1'; 423 let order = trackEls.map((_, i) => i); 424 425 function buildOrder() { 426 order = trackEls.map((_, i) => i); 427 if (shuffled) order.sort(() => Math.random() - 0.5); 428 } 429 buildOrder(); 430 431 const btnPlay = wrap.querySelector('.wapp-pl-btn-play'); 432 const btnPrev = wrap.querySelector('.wapp-pl-btn-prev'); 433 const btnNext = wrap.querySelector('.wapp-pl-btn-next'); 434 const btnShuffle = wrap.querySelector('.wapp-pl-btn-shuffle'); 435 const btnLoop = wrap.querySelector('.wapp-pl-btn-loop'); 436 const progFill = wrap.querySelector('.wapp-pl-progress-fill'); 437 const progThumb = wrap.querySelector('.wapp-pl-progress-thumb'); 438 const progWrap = wrap.querySelector('.wapp-pl-progress'); 439 const timeCur = wrap.querySelector('.wapp-pl-time-cur'); 440 const timeTot = wrap.querySelector('.wapp-pl-time-tot'); 441 const titleEl = wrap.querySelector('.wapp-pl-title'); 442 const artistEl = wrap.querySelector('.wapp-pl-artist'); 443 const imgEl = wrap.querySelector('.wapp-pl-img'); 444 445 if (shuffled && btnShuffle) btnShuffle.classList.add('active'); 446 447 function loadTrack(idx, andPlay) { 448 const el = trackEls[idx]; 449 if (!el) return; 450 current = idx; 451 const td = el.dataset; 452 audio.src = td.url || ''; audio.preload = 'auto'; audio.load(); 453 if (titleEl) titleEl.textContent = td.title || 'â'; 454 if (artistEl) artistEl.textContent = td.artist || ''; 455 if (imgEl) { 456 if (td.image) { imgEl.src = td.image; imgEl.style.display = ''; } 457 else imgEl.style.display = 'none'; 458 } 459 trackEls.forEach(t => t.classList.remove('active')); 460 el.classList.add('active'); 461 el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); 462 if (andPlay) tryAutoplay(audio, wrap.querySelector('.wapp-pl-player') || wrap, 80, null); 463 } 464 465 function playAdj(dir) { 466 const ni = (order.indexOf(current) + dir + order.length) % order.length; 467 loadTrack(order[ni], true); 468 } 469 470 function updateProg() { 471 const pct = audio.duration ? (audio.currentTime / audio.duration) * 100 : 0; 472 if (progFill) progFill.style.width = pct + '%'; 473 if (progThumb) progThumb.style.left = pct + '%'; 474 if (timeCur) timeCur.textContent = fmt(audio.currentTime); 475 if (timeTot) timeTot.textContent = fmt(audio.duration); 476 } 477 478 function setUI(playing) { 479 if (btnPlay) btnPlay.textContent = playing ? 'â¸' : 'â¶'; 480 wrap.classList.toggle('wapp-playing', playing); 481 } 482 483 audio.addEventListener('timeupdate', updateProg); 484 audio.addEventListener('loadedmetadata', updateProg); 485 audio.addEventListener('play', () => setUI(true)); 486 audio.addEventListener('pause', () => setUI(false)); 487 audio.addEventListener('ended', () => { 488 if (looping) { audio.play(); return; } 489 if (d.continuous === '1') playAdj(1); else setUI(false); 490 }); 491 492 if (btnPlay) btnPlay.addEventListener('click', () => { 493 if (current < 0) { loadTrack(order[0], true); return; } 494 audio.paused ? audio.play().catch(() => {}) : audio.pause(); 495 }); 496 if (btnNext) btnNext.addEventListener('click', () => playAdj(1)); 497 if (btnPrev) btnPrev.addEventListener('click', () => { 498 if (audio.currentTime > 3) { audio.currentTime = 0; return; } 499 playAdj(-1); 500 }); 501 if (btnShuffle) btnShuffle.addEventListener('click', () => { 502 shuffled = !shuffled; btnShuffle.classList.toggle('active', shuffled); buildOrder(); 503 }); 504 if (btnLoop) btnLoop.addEventListener('click', () => { 505 looping = !looping; btnLoop.classList.toggle('active', looping); 506 }); 507 if (progWrap) progWrap.addEventListener('click', e => { 508 const r = progWrap.getBoundingClientRect(); 509 if (audio.duration) audio.currentTime = ((e.clientX - r.left) / r.width) * audio.duration; 510 }); 511 512 trackEls.forEach(el => { 513 el.addEventListener('click', e => { 514 if (e.target.closest('.wapp-pl-act-btn')) return; 515 const ti = parseInt(el.dataset.index); 516 if (ti === current && !audio.paused) { audio.pause(); return; } 517 loadTrack(ti, true); 518 }); 519 const shareBtn = el.querySelector('.wapp-pl-share-btn'); 520 if (shareBtn) shareBtn.addEventListener('click', e => { 521 e.stopPropagation(); 522 const url = el.dataset.url || shareBtn.dataset.url || window.location.href; 523 const title = el.dataset.title || ''; 524 toggleShareMenu(shareBtn, url, title); 525 }); 526 }); 527 528 if (d.autoplay === '1' && trackEls.length) { 529 const doAP = () => loadTrack(order[0], true); 530 if (document.readyState !== 'loading') doAP(); 531 else document.addEventListener('DOMContentLoaded', doAP, { once: true }); 532 } 533 } 534 535 /* ââ Boot âââââââââââââââââââââââââââââââââââââââââââââââ */ 536 function boot() { 537 document.querySelectorAll('.wapp-player-wrap').forEach(initPlayer); 538 document.querySelectorAll('.wapp-playlist-wrap').forEach(initPlaylist); 539 } 540 541 if (document.readyState === 'loading') { 542 document.addEventListener('DOMContentLoaded', boot); 543 } else {
544 boot(); 545 } 546 547})();
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.