1jQuery(function ($) { 2 3 //alert("Found players:" + $('.audioPlayer').length); 4 5 // -------------------------------------------- 6 // Track all audio elements so we can pause others 7 // -------------------------------------------- 8 var allAudios = []; 9 10 // The player the user last interacted with 11 var activeWrapper = null; 12 13 // -------------------------------------------- 14 // Initialize each custom audio player 15 // -------------------------------------------- 16 $('.audioPlayer').each(function () { 17 18 var $wrapper = $(this); 19 var audioSelector = $wrapper.data('audio'); 20 var audio = $(audioSelector).get(0); 21 22 // If something is wrong or missing, skip this one 23 if (!audio) { 24 return; 25 } 26 27 // Keep a reference so we can auto-pause the others 28 allAudios.push(audio); 29 30 // Cache UI elements 31 var $playToggle = $wrapper.find('.playToggle'); 32 var $scrubBar = $wrapper.find('.scrubBar'); 33 var $scrubProgress = $wrapper.find('.scrubProgress'); 34 var $volumeControl = $wrapper.find('.volumeControl'); 35 var $volumeIcon = $wrapper.find('.volumeIcon'); 36 var $volumeSlider = $wrapper.find('.volumeSlider'); 37 38 var $currentTime = $wrapper.find('.currentTime'); 39 var $duration = $wrapper.find('.duration'); 40 41 // Default volume 42 audio.volume = 1; 43 44 // --------------------------------------------------- 45 // Mark this player as the active one (for keystrokes) 46 // --------------------------------------------------- 47 function setActive() { 48 activeWrapper = $wrapper; 49 $('.audioPlayer').removeClass('is-active'); 50 $wrapper.addClass('is-active'); 51 } 52 53 // --------------------------------------------------- 54 // Format seconds â mm:ss 55 // --------------------------------------------------- 56 function formatTime(secs) { 57 var seconds = Math.floor(secs); 58 var minutes = Math.floor(seconds / 60); 59 var remaining = seconds % 60; 60 var result = minutes + ':'; 61 62 if (remaining < 10) { 63 result += '0' + remaining; 64 } else { 65 result += remaining; 66 } 67 68 return result; 69 } 70 71 // --------------------------------------------------- 72 // Update the play/pause icon 73 // --------------------------------------------------- 74 function updatePlayIcon() { 75 if (audio.paused) { 76 $playToggle.html('<i class="fa-solid fa-play"></i>'); 77 } else { 78 $playToggle.html('<i class="fa-solid fa-pause"></i>'); 79 } 80 } 81 82 // --------------------------------------------------- 83 // Update the volume icon based on current volume level 84 // --------------------------------------------------- 85 function updateVolumeIcon() { 86 var value; 87 88 if (audio.muted === true) { 89 value = 0; 90 } else { 91 value = audio.volume; 92 } 93 94 if (value === 0) { 95 $volumeIcon.html('<i class="fa-solid fa-volume-xmark"></i>'); 96 } else if (value < 0.5) { 97 $volumeIcon.html('<i class="fa-solid fa-volume-low"></i>'); 98 } else { 99 $volumeIcon.html('<i class="fa-solid fa-volume-high"></i>'); 100 } 101 } 102 103 // Highlight this player when clicked 104 $wrapper.on('mousedown click', function () { 105 setActive(); 106 }); 107 108 // --------------------------------------------------- 109 // Play/pause button 110 // --------------------------------------------------- 111 $playToggle.on('click', function () { 112 setActive(); 113 //alert("clicked"); 114 if (audio.paused) { 115 audio.play(); 116 } else { 117 audio.pause(); 118 } 119 }); 120 121 // --------------------------------------------------- 122 // Auto-pause other players when this one plays 123 // --------------------------------------------------- 124 $(audio).on('play', function () { 125 var i; 126 for (i = 0; i < allAudios.length; i++) { 127 if (allAudios[i] !== audio) { 128 if (allAudios[i].paused === false) { 129 allAudios[i].pause(); 130 } 131 } 132 } 133 updatePlayIcon(); 134 }); 135 136 // Update icon when paused 137 $(audio).on('pause', function () { 138 updatePlayIcon(); 139 }); 140 141 // --------------------------------------------------- 142 // Duration (set once metadata is loaded) 143 // --------------------------------------------------- 144 $(audio).on('loadedmetadata', function () { 145 if ($duration.length > 0) { 146 $duration.text(formatTime(audio.duration)); 147 } 148 }); 149 150 // --------------------------------------------------- 151 // As audio plays, update the progress bar + time label 152 // --------------------------------------------------- 153 $(audio).on('timeupdate', function () { 154 if (!audio.duration) { 155 return; 156 } 157 158 var percent = (audio.currentTime / audio.duration) * 100; 159 $scrubProgress.css('width', percent + '%'); 160 161 if ($currentTime.length > 0) { 162 $currentTime.text(formatTime(audio.currentTime)); 163 } 164 }); 165 166 // --------------------------------------------------- 167 // Click-to-seek on the scrub bar 168 // --------------------------------------------------- 169 $scrubBar.on('click', function (e) { 170 if (!audio.duration) { 171 return; 172 } 173 174 setActive(); 175 176 var rect = this.getBoundingClientRect(); 177 var offsetX = e.clientX - rect.left; 178 var ratio = offsetX / rect.width; 179 180 if (ratio < 0) { 181 ratio = 0; 182 } 183 184 if (ratio > 1) { 185 ratio = 1; 186 } 187 188 audio.currentTime = ratio * audio.duration; 189 }); 190 191 // --------------------------------------------------- 192 // Volume slider changes 193 // --------------------------------------------------- 194 $volumeSlider.on('input change', function () { 195 var value = parseFloat(this.value); 196 197 if (isNaN(value)) { 198 return; 199 } 200 201 audio.volume = value; 202 203 if (value === 0) { 204 audio.muted = true; 205 } else { 206 audio.muted = false;
207 } 208 209 updateVolumeIcon(); 210 }); 211 212 // --------------------------------------------------- 213 // Keep slider + icon synced with actual volume 214 // --------------------------------------------------- 215 $(audio).on('volumechange', function () { 216 if (audio.muted === true) { 217 $volumeSlider.val(0); 218 } else { 219 $volumeSlider.val(audio.volume); 220 } 221 updateVolumeIcon(); 222 }); 223 224 // --------------------------------------------------- 225 // Volume popup toggle 226 // --------------------------------------------------- 227 /* 228 $volumeIcon.on('click', function (e) { 229 e.stopPropagation(); 230 setActive(); 231 232 if ($volumeControl.hasClass('is-open')) { 233 $volumeControl.removeClass('is-open'); 234 } else { 235 $volumeControl.addClass('is-open'); 236 } 237 }); 238 */ 239 240 // replace the old $volumeIcon.on('click', ...) block with this: 241 $volumeControl.on('click', function (e) { 242 //alert("clicked"); 243 e.stopPropagation(); 244 setActive(); 245 246 if ($volumeControl.hasClass('is-open')) { 247 $volumeControl.removeClass('is-open'); 248 } else { 249 $volumeControl.addClass('is-open'); 250 } 251 }); 252 253 254 // Store for keyboard shortcuts 255 $wrapper.data('audioElement', audio); 256 $wrapper.data('updatePlayIcon', updatePlayIcon); 257 $wrapper.data('updateVolumeIcon', updateVolumeIcon); 258 259 // Initial icon states 260 updatePlayIcon(); 261 updateVolumeIcon(); 262 }); 263 264 // If user hasnât interacted yet, make the first one active 265 if (!activeWrapper || activeWrapper.length === 0) { 266 activeWrapper = $('.audioPlayer').first(); 267 activeWrapper.addClass('is-active'); 268 } 269 270 // --------------------------------------------------- 271 // Close any open volume popup when clicking outside 272 // --------------------------------------------------- 273 $(document).on('click', function (e) { 274 var $target = $(e.target); 275 var insideVolume = $target.closest('.volumeControl').length > 0; 276 277 if (!insideVolume) { 278 $('.volumeControl').removeClass('is-open'); 279 } 280 }); 281 282 // --------------------------------------------------- 283 // GLOBAL KEYBOARD SHORTCUTS 284 // Applies to whichever player was last clicked 285 // --------------------------------------------------- 286 $(document).on('keydown', function (e) { 287 288 if (!activeWrapper) { 289 return; 290 } 291 292 // Donât hijack keyboard when typing in a form field 293 var tagName = e.target.tagName; 294 295 if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') { 296 return; 297 } 298 299 var audio = activeWrapper.data('audioElement'); 300 var updatePlayIcon = activeWrapper.data('updatePlayIcon'); 301 var updateVolumeIcon = activeWrapper.data('updateVolumeIcon'); 302 303 if (!audio) { 304 return; 305 } 306 307 var key = e.key; 308 309 // Space or "K" 310 if (key === ' ' || key === 'k') { 311 e.preventDefault(); 312 313 if (audio.paused) { 314 audio.play(); 315 } else { 316 audio.pause(); 317 } 318 319 updatePlayIcon(); 320 } 321 322 // Right arrow (seek forward) 323 else if (key === 'ArrowRight') { 324 e.preventDefault(); 325 326 if (audio.duration) { 327 var forwardTime = audio.currentTime + 5; 328 329 if (forwardTime > audio.duration) { 330 forwardTime = audio.duration; 331 } 332 333 audio.currentTime = forwardTime; 334 } 335 } 336 337 // Left arrow (seek backward) 338 else if (key === 'ArrowLeft') { 339 e.preventDefault(); 340 341 var backwardTime = audio.currentTime - 5; 342 343 if (backwardTime < 0) { 344 backwardTime = 0; 345 } 346 347 audio.currentTime = backwardTime; 348 } 349 350 // Volume up 351 else if (key === 'ArrowUp') { 352 e.preventDefault(); 353 354 var upVolume = audio.volume + 0.05; 355 356 if (upVolume > 1) { 357 upVolume = 1; 358 } 359 360 audio.volume = upVolume; 361 362 if (audio.volume === 0) { 363 audio.muted = true; 364 } else { 365 audio.muted = false; 366 } 367 368 updateVolumeIcon(); 369 } 370 371 // Volume down 372 else if (key === 'ArrowDown') { 373 e.preventDefault(); 374 375 var downVolume = audio.volume - 0.05; 376 377 if (downVolume < 0) { 378 downVolume = 0; 379 } 380 381 audio.volume = downVolume; 382 383 if (audio.volume === 0) { 384 audio.muted = true; 385 } else { 386 audio.muted = false; 387 } 388 389 updateVolumeIcon(); 390 } 391 392 // Mute toggle 393 else if (key === 'm' || key === 'M') { 394 e.preventDefault(); 395 396 if (audio.muted === true) { 397 audio.muted = false;
398 } else { 399 audio.muted = true; 400 } 401 402 updateVolumeIcon(); 403 } 404 }); 405 406 407 408 409}); 410 411
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.