1/** 2 * wf_audio 3 * ---------------------------------------------------------------------------------------------------- 4 * Plugin ligero en JS puro para reproducción de audio con comportamiento toggle (play/stop). 5 * 6 * FUNCIONALIDAD EXTENDIDA 7 * ---------------------------------------------------------------------------------------------------- 8 * - Permite reproducir audio desde: 9 * 1. URL definida en el script 10 * 2. Atributo data-url del botón 11 * 3. Atributo data-audio del botón 12 * 13 * - Usa delegación de eventos (compatible con contenido dinámico AJAX). 14 * - Un único estado: 15 * · Si está reproduciendo â lo para 16 * · Si está parado â lo reproduce 17 * 18 * - Sistema de depuración con console.group (estilo Webforever). 19 * - Soporte para múltiples botones. 20 * - Actualiza estado visual automáticamente (clases + texto). 21 * - No rompe si no hay URL â salida segura. 22 * 23 * USO BÃSICO 24 * ---------------------------------------------------------------------------------------------------- 25 * HTML: 26 * 27 * <button 28 * class='wf_audio_play' 29 * data-url='/data/dms/audio.mp3' 30 * data-texto-play='Reproducir' 31 * data-texto-stop='Parar' 32 * > 33 * Reproducir 34 * </button> 35 * 36 * JS: 37 * 38 * document.addEventListener(`DOMContentLoaded`, () => 39 * { 40 * new wf_audio({ 41 * selector_boton : `.wf_audio_play`, 42 * url : ``, 43 * debug : true 44 * }); 45 * }); 46 * 47 * 48 * OPCIONES 49 * ---------------------------------------------------------------------------------------------------- 50 * @param string selector_boton Selector CSS del botón (por defecto `.wf_audio_play`) 51 * @param string url URL fija de audio (opcional) 52 * @param bool debug Activa logs en consola 53 * 54 * 55 * COMPORTAMIENTO 56 * ---------------------------------------------------------------------------------------------------- 57 * - Click â toggle play/stop 58 * - Cambia texto del botón si existen: 59 * data-texto-play 60 * data-texto-stop 61 * 62 * - Clases automáticas: 63 * wf_audio_on 64 * wf_audio_off 65 * 66 * 67 * EVENTOS INTERNOS 68 * ---------------------------------------------------------------------------------------------------- 69 * play â marca como reproduciendo 70 * pause â marca como detenido 71 * ended â reinicia estado 72 * error â muestra error en consola 73 * 74 * 75 * SEGURIDAD 76 * ---------------------------------------------------------------------------------------------------- 77 * - Si no hay URL válida â no hace nada 78 * - Evita múltiples instancias de audio innecesarias 79 * 80 * 81 * EJEMPLO AVANZADO (URL DESDE SCRIPT) 82 * ---------------------------------------------------------------------------------------------------- 83 * new wf_audio({ 84 * selector_boton : `.play`, 85 * url : `/data/dms/8bit-title-screen.mp3` 86 * }); 87 * 88 * 89 * ESTILOS OPCIONALES 90 * ---------------------------------------------------------------------------------------------------- 91 * .wf_audio_on { opacity: 1; } 92 * .wf_audio_off { opacity: .85; } 93 * 94 * 95 * REGISTRO DE CAMBIOS 96 * ---------------------------------------------------------------------------------------------------- 97 * 2026-04-16 : Versión inicial JS puro (sustituye plugin jQuery + Rythm parcial) 98 * 99 * 100 * REFERENCIAS 101 * ---------------------------------------------------------------------------------------------------- 102 * wf.js / sistema Webforever 103 * 104 * 105 * DERECHOS 106 * ---------------------------------------------------------------------------------------------------- 107 * @author Pablo Manuel Burrero Sánchez <[email protected]> 108 * @copyright Copyright (c) 2005-2026 109 */ 110class wf_audio 111{ 112 constructor(opciones = {}) 113 { 114 // -------- Declaraciones -------- 115 this.selector_boton = opciones.selector_boton || `.wf_audio_play`; 116 this.url = opciones.url || ``; 117 this.debug = opciones.debug ?? true; 118 this.audio = null; 119 this.reproduciendo = false; 120 this.inicializado = false; 121 122 // -------- Inicializaciones -------- 123 this.init(); 124 } 125 126 debug_group(titulo = `wf_audio`, datos = null) 127 { 128 if (!this.debug) return; 129 130 console.groupCollapsed(`%c${titulo}`, `color:#0d6efd;font-weight:bold;`); 131 if (datos !== null) console.log(datos); 132 console.groupEnd(); 133 } 134 135 debug_log(texto = ``, valor = null) 136 { 137 if (!this.debug) return; 138 139 if (valor === null) console.log(`%c${texto}`, `color:#198754;`); 140 else console.log(`%c${texto}`, `color:#198754;`, valor); 141 } 142 143 debug_warn(texto = ``, valor = null) 144 { 145 if (!this.debug) return; 146 147 if (valor === null) console.warn(`%c${texto}`, `color:#dc3545;font-weight:bold;`); 148 else console.warn(`%c${texto}`, `color:#dc3545;font-weight:bold;`, valor); 149 } 150 151 init() 152 { 153 this.debug_group(`wf_audio.init()`, { 154 selector_boton : this.selector_boton, 155 url_script : this.url 156 }); 157 158 document.addEventListener(`click`, ev => 159 { 160 const boton = ev.target.closest(this.selector_boton); 161 if (!boton) return; 162 163 ev.preventDefault(); 164 this.toggle(boton);
165 }); 166 167 this.inicializado = true; 168 this.debug_log(`Plugin de audio inicializado correctamente.`); 169 } 170 171 obtener_url(boton = null) 172 { 173 // Prioridad: 174 // 1. URL pasada por script 175 // 2. data-url del botón pulsado 176 // 3. data-audio del botón pulsado 177 178 if (this.url && this.url.trim() !== ``) 179 return this.url.trim(); 180 181 if (boton?.dataset?.url && boton.dataset.url.trim() !== ``) 182 return boton.dataset.url.trim(); 183 184 if (boton?.dataset?.audio && boton.dataset.audio.trim() !== ``) 185 return boton.dataset.audio.trim(); 186 187 return ``; 188 } 189 190 preparar_audio(url = ``) 191 { 192 this.debug_group(`wf_audio.preparar_audio()`, { url }); 193 194 if (!url) 195 { 196 this.debug_warn(`No se ha indicado URL de audio. Salimos.`); 197 return false; 198 } 199 200 // Si no existe audio o la URL cambió, creamos uno nuevo 201 if (!this.audio || this.audio.src !== new URL(url, window.location.href).href) 202 { 203 if (this.audio) 204 { 205 this.audio.pause(); 206 this.audio.src = ``; 207 this.audio.load(); 208 } 209 210 this.audio = new Audio(url); 211 this.audio.preload = `auto`; 212 213 this.audio.addEventListener(`ended`, () => 214 { 215 this.reproduciendo = false; 216 this.debug_log(`Audio finalizado.`); 217 this.actualizar_botones(); 218 }); 219 220 this.audio.addEventListener(`play`, () => 221 { 222 this.reproduciendo = true; 223 this.debug_log(`Audio en reproducción.`); 224 this.actualizar_botones(); 225 }); 226 227 this.audio.addEventListener(`pause`, () => 228 { 229 this.reproduciendo = false; 230 this.debug_log(`Audio pausado.`); 231 this.actualizar_botones(); 232 }); 233 234 this.audio.addEventListener(`error`, err => 235 { 236 this.reproduciendo = false; 237 this.debug_warn(`Error al cargar o reproducir el audio.`, err); 238 this.actualizar_botones(); 239 }); 240 241 this.debug_log(`Objeto Audio creado.`, this.audio); 242 } 243 244 return true; 245 } 246 247 async reproducir(boton = null) 248 { 249 const url = this.obtener_url(boton); 250 251 this.debug_group(`wf_audio.reproducir()`, { 252 url, 253 reproduciendo: this.reproduciendo 254 }); 255 256 if (!this.preparar_audio(url)) return false; 257 258 try 259 { 260 await this.audio.play(); 261 this.reproduciendo = true; 262 this.actualizar_botones(); 263 return true; 264 } 265 catch (error) 266 { 267 this.reproduciendo = false; 268 this.debug_warn(`No se pudo iniciar la reproducción.`, error); 269 this.actualizar_botones(); 270 return false; 271 } 272 } 273 274 parar() 275 { 276 this.debug_group(`wf_audio.parar()`, { 277 hay_audio: !!this.audio, 278 reproduciendo: this.reproduciendo 279 }); 280 281 if (!this.audio) 282 { 283 this.debug_warn(`No hay audio cargado. Salimos.`); 284 return false; 285 } 286 287 this.audio.pause(); 288 this.audio.currentTime = 0; 289 this.reproduciendo = false; 290 this.actualizar_botones(); 291 292 this.debug_log(`Audio detenido y reiniciado.`); 293 return true; 294 } 295 296 toggle(boton = null) 297 { 298 this.debug_group(`wf_audio.toggle()`, { 299 reproduciendo: this.reproduciendo, 300 boton 301 }); 302 303 if (this.reproduciendo) this.parar(); 304 else this.reproducir(boton); 305 } 306 307 actualizar_botones() 308 { 309 const botones = document.querySelectorAll(this.selector_boton); 310 311 botones.forEach(boton => 312 { 313 boton.classList.toggle(`wf_audio_on`, this.reproduciendo); 314 boton.classList.toggle(`wf_audio_off`, !this.reproduciendo); 315 boton.setAttribute(`aria-pressed`, this.reproduciendo ? `true` : `false`); 316 317 if (boton.dataset.textoPlay || boton.dataset.textoStop) 318 boton.textContent = this.reproduciendo 319 ? (boton.dataset.textoStop || `Parar`) 320 : (boton.dataset.textoPlay || `Reproducir`); 321 }); 322 } 323}
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.