| import { Howl, Howler } from 'howler'; |
| |
| // seek tolerance in seconds, keeps the Howl player from seeking unnecessarily |
| // if the number is too small, Howl.seek() is called too often and creates a popping noise |
| // too large and audio layers may be skipped over |
| const kTolerance = 0.75; |
| |
| /** |
| * AudioPlayers wrap a howl and control playback through seek calls |
| * |
| * @param source - URL or base64 data URI pointing to audio data |
| * @param format - only needed if extension is not provided by source (inline URI) |
| * |
| */ |
| export class AudioPlayer { |
| private playing: boolean = false; |
| |
| private howl: Howl; |
| |
| constructor(source: string) { |
| this.howl = new Howl({ |
| src: [source], |
| preload: true, |
| }); |
| } |
| |
| pause(): void { |
| if (this.playing) { |
| this.howl.pause(); |
| this.playing = false; |
| } |
| } |
| |
| seek(t: number): void { |
| if (!this.playing && t >= 0) { |
| // Sometimes browsers will prevent the audio from playing. |
| // We need to resume the AudioContext or it will never play. |
| if (Howler.ctx.state === 'suspended') { |
| Howler.ctx.resume().then(() => this.howl.play()); |
| } else { |
| this.howl.play(); |
| } |
| this.playing = true; |
| } |
| |
| if (this.playing) { |
| if (t < 0) { |
| this.howl.stop(); |
| this.playing = false; |
| } else { |
| const playerPos = this.howl.seek() as number; |
| |
| if (Math.abs(playerPos - t) > kTolerance) { |
| this.howl.seek(t); |
| } |
| } |
| } |
| } |
| |
| volume(v: number): void { |
| this.howl.volume(v); |
| } |
| } |
| |
| export class SoundMap { |
| map: Map<string, AudioPlayer> = new Map() |
| |
| setPlayer(name: string, player: AudioPlayer): void { |
| this.map.set(name, player); |
| } |
| |
| getPlayer(name: string): AudioPlayer { |
| return this.map.get(name)!; |
| } |
| |
| pause(): void { |
| for (const player of this.map.values()) { |
| player.pause(); |
| } |
| } |
| |
| stop(): void { |
| for (const player of this.map.values()) { |
| player.seek(-1); |
| } |
| } |
| |
| setVolume(v: number): void { |
| Howler.volume(v); |
| } |
| } |