Ruleta actvidad completa en React

This commit is contained in:
2026-07-25 01:35:36 -06:00
commit 3ce44d7aac
49 changed files with 4449 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import { Howler } from 'howler'
import { ExtendedHowl, createSound, stopAllSounds } from './ExtendedHowl'
import { AUDIOS } from './audios'
export class AudioController {
private audioElement: ExtendedHowl | null = null
private isMuted = false
load(url: string): void {
this.audioElement?.stop()
this.audioElement = createSound(url)
}
setHowl(howl: ExtendedHowl): void {
this.audioElement?.stop()
this.audioElement = howl
}
stopAllAndPlay(howl: ExtendedHowl): void {
stopAllSounds()
this.setHowl(howl)
this.play()
}
play(): void {
this.audioElement?.play()
}
pause(): void {
this.audioElement?.pause()
}
stop(): void {
this.audioElement?.stop()
}
toggle(): void {
if (!this.audioElement) return
this.audioElement.playing() ? this.pause() : this.play()
}
toggleMute(): void {
this.isMuted = !this.isMuted
Howler.mute(this.isMuted)
}
isPlaying(): boolean {
return this.audioElement?.playing() ?? false
}
getMuted(): boolean {
return this.isMuted
}
getDuration(): number {
return (this.audioElement?.duration() as number) ?? 0
}
onTimeUpdate(cb: (currentTime: number) => void): void {
this.audioElement?.onTimeUpdate(cb)
}
offTimeUpdate(cb: (currentTime: number) => void): void {
this.audioElement?.offTimeUpdate(cb)
}
on(event: string, cb: () => void): void {
this.audioElement?.on(event, cb)
}
}
export const audioController = new AudioController()
// Sonidos precargados listos para usar
export const sounds = {
click: createSound(AUDIOS.click),
wheelSpin: createSound(AUDIOS.wheelSpin),
correct: createSound(AUDIOS.correct),
incorrect: createSound(AUDIOS.incorrect),
}
+69
View File
@@ -0,0 +1,69 @@
import { Howl, Howler } from 'howler'
type TimeUpdateCallback = (currentTime: number) => void
declare module 'howler' {
interface HowlerGlobal {
_howls: Howl[]
}
}
export class ExtendedHowl extends Howl {
private _timeupdateListeners: TimeUpdateCallback[] = []
private _interval: ReturnType<typeof setInterval> | null = null
constructor(options: ConstructorParameters<typeof Howl>[0]) {
super(options)
this._startTimeUpdate()
}
private _startTimeUpdate(): void {
this._interval = setInterval(() => {
if (this.playing()) this._emitTimeUpdate(this.seek() as number)
}, 250)
}
private _emitTimeUpdate(currentTime: number): void {
this._timeupdateListeners.forEach((cb) => cb(currentTime))
}
onTimeUpdate(cb: TimeUpdateCallback): void {
this._timeupdateListeners.push(cb)
}
offTimeUpdate(cb: TimeUpdateCallback): void {
this._timeupdateListeners = this._timeupdateListeners.filter((l) => l !== cb)
}
play(id?: number): number {
const result = super.play(id as number)
if (!this._interval) this._startTimeUpdate()
return result
}
pause(id?: number): this {
super.pause(id)
if (this._interval) {
clearInterval(this._interval)
this._interval = null
}
return this
}
stop(id?: number): this {
super.stop(id)
if (this._interval) {
clearInterval(this._interval)
this._interval = null
}
return this
}
}
export function createSound(audioUrl?: string): ExtendedHowl | null {
return audioUrl ? new ExtendedHowl({ src: [audioUrl] }) : null
}
export function stopAllSounds(): void {
Howler._howls?.forEach((sound: Howl) => sound.stop())
}
+6
View File
@@ -0,0 +1,6 @@
export const AUDIOS = {
click: '/audios/click.mp3',
wheelSpin: '/audios/wheel-spin.mp3',
correct: '/audios/feedback-correct.mpeg',
incorrect: '/audios/feedback-incorrect.mpeg',
} as const