1class SoundPlayer { 2 constructor({ soundsApiUrl = '/api/sounds', soundsBaseUrl = '/sounds', delayMs = 2000 } = {}) { 3 this.soundsApiUrl = soundsApiUrl; 4 this.soundsBaseUrl = soundsBaseUrl; 5 this.delayMs = delayMs; 6 this.queue = []; 7 this.audio = null; 8 this.stopped = false; 9 this.timeout = null; // â ADD THIS 10 11 } 12 13 // Fisher-Yates shuffle 14 _shuffle(array) { 15 const arr = [...array]; 16 for (let i = arr.length - 1; i > 0; i--) { 17 const j = Math.floor(Math.random() * (i + 1)); 18 [arr[i], arr[j]] = [arr[j], arr[i]]; 19 } 20 return arr; 21 } 22 23 async _fetchQueue() { 24 const response = await fetch(this.soundsApiUrl); 25 if (!response.ok) throw new Error('Failed to fetch sound list'); 26 const files = await response.json(); 27 if (!files.length) throw new Error('No sound files found'); 28 this.queue = this._shuffle(files); // randomized on each page visit 29 } 30 31 _playNext() { 32 if (this.stopped || this.queue.length === 0) return; 33 34 const file = this.queue.shift(); // remove from queue as we go 35 const url = `${this.soundsBaseUrl}/${file}`; 36 37 console.log(`[SoundPlayer] Playing: ${file} (${this.queue.length} remaining)`); 38 39 this.audio = new Audio(url); 40 41 this.audio.addEventListener('ended', () => { 42 if (this.queue.length === 0) { 43 console.log('[SoundPlayer] All sounds played.'); 44 return; 45 } 46 setTimeout(() => this._playNext(), this.delayMs); 47 }); 48 49 this.audio.addEventListener('error', (e) => { 50 console.warn(`[SoundPlayer] Error playing ${file}:`, e); 51 setTimeout(() => this._playNext(), this.delayMs); // skip broken file 52 }); 53 54 this.audio.play().catch(err => { 55 // Autoplay was blocked by the browser â wait for user interaction 56 console.warn('[SoundPlayer] Autoplay blocked. Waiting for user gesture...'); 57 const resume = () => { 58 this.audio.play(); 59 document.removeEventListener('click', resume); 60 document.removeEventListener('keydown', resume); 61 }; 62 document.addEventListener('click', resume, { once: true }); 63 document.addEventListener('keydown', resume, { once: true }); 64 }); 65 } 66 67 async start() { 68 this.stopped = false; 69 await this._fetchQueue(); 70 this._playNext(); 71 } 72 73 stop() { 74 this.stopped = true; 75 clearTimeout(this.timeout); 76 if (this.audio) { 77 this.audio.pause(); 78 this.audio.src = ''; 79 } 80 $('#mindful-soundholder').remove() 81 } 82 83 toggle() { 84 if (this.stopped || (this.audio && this.audio.paused)) { 85 this.stopped = false; 86 if (this.audio) { 87 this.audio.play() 88 } else { 89 this._playNext() // no audio yet, start fresh 90 } 91 } else { 92 this.stopped = true; 93 clearTimeout(this.timeout); 94 if (this.audio) this.audio.pause(); 95 } 96 } 97}
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.