| |
| |
| |
| |
| |
| |
| |
|
|
| import { db } from '@/lib/utils/database'; |
| import { createLogger } from '@/lib/logger'; |
|
|
| const log = createLogger('AudioPlayer'); |
|
|
| |
| |
| |
| export class AudioPlayer { |
| private audio: HTMLAudioElement | null = null; |
| private onEndedCallback: (() => void) | null = null; |
| private muted: boolean = false; |
| private volume: number = 1; |
| private playbackRate: number = 1; |
|
|
| |
| |
| |
| |
| |
| |
| public async play(audioId: string, audioUrl?: string): Promise<boolean> { |
| try { |
| |
| if (audioUrl) { |
| this.stop(); |
| this.audio = new Audio(); |
| this.audio.src = audioUrl; |
| if (this.muted) this.audio.volume = 0; |
| else this.audio.volume = this.volume; |
| this.audio.defaultPlaybackRate = this.playbackRate; |
| this.audio.playbackRate = this.playbackRate; |
| this.audio.addEventListener('ended', () => { |
| this.onEndedCallback?.(); |
| }); |
| await this.audio.play(); |
| this.audio.playbackRate = this.playbackRate; |
| return true; |
| } |
|
|
| |
| const audioRecord = await db.audioFiles.get(audioId); |
|
|
| if (!audioRecord) { |
| |
| return false; |
| } |
|
|
| |
| this.stop(); |
|
|
| |
| this.audio = new Audio(); |
|
|
| |
| const blobUrl = URL.createObjectURL(audioRecord.blob); |
| this.audio.src = blobUrl; |
| if (this.muted) this.audio.volume = 0; |
| else this.audio.volume = this.volume; |
|
|
| |
| this.audio.defaultPlaybackRate = this.playbackRate; |
| this.audio.playbackRate = this.playbackRate; |
|
|
| |
| this.audio.addEventListener('ended', () => { |
| URL.revokeObjectURL(blobUrl); |
| this.onEndedCallback?.(); |
| }); |
|
|
| |
| await this.audio.play(); |
| |
| this.audio.playbackRate = this.playbackRate; |
| return true; |
| } catch (error) { |
| log.error('Failed to play audio:', error); |
| throw error; |
| } |
| } |
|
|
| |
| |
| |
| public pause(): void { |
| if (this.audio && !this.audio.paused) { |
| this.audio.pause(); |
| } |
| } |
|
|
| |
| |
| |
| public stop(): void { |
| if (this.audio) { |
| this.audio.pause(); |
| this.audio.currentTime = 0; |
| this.audio = null; |
| } |
| |
| |
| |
| } |
|
|
| |
| |
| |
| public resume(): void { |
| if (this.audio?.paused) { |
| this.audio.playbackRate = this.playbackRate; |
| this.audio.play().catch((error) => { |
| log.error('Failed to resume audio:', error); |
| }); |
| } |
| } |
|
|
| |
| |
| |
| public isPlaying(): boolean { |
| return this.audio !== null && !this.audio.paused; |
| } |
|
|
| |
| |
| |
| |
| public hasActiveAudio(): boolean { |
| return this.audio !== null; |
| } |
|
|
| |
| |
| |
| public getCurrentTime(): number { |
| return this.audio ? this.audio.currentTime * 1000 : 0; |
| } |
|
|
| |
| |
| |
| public getDuration(): number { |
| return this.audio && !isNaN(this.audio.duration) ? this.audio.duration * 1000 : 0; |
| } |
|
|
| |
| |
| |
| public onEnded(callback: () => void): void { |
| this.onEndedCallback = callback; |
| } |
|
|
| |
| |
| |
| public setMuted(muted: boolean): void { |
| this.muted = muted; |
| if (this.audio) { |
| this.audio.volume = muted ? 0 : this.volume; |
| } |
| } |
|
|
| |
| |
| |
| public setVolume(volume: number): void { |
| this.volume = Math.max(0, Math.min(1, volume)); |
| if (this.audio && !this.muted) { |
| this.audio.volume = this.volume; |
| } |
| } |
|
|
| |
| |
| |
| public setPlaybackRate(rate: number): void { |
| this.playbackRate = Math.max(0.5, Math.min(2, rate)); |
| if (this.audio) { |
| this.audio.playbackRate = this.playbackRate; |
| } |
| } |
|
|
| |
| |
| |
| public destroy(): void { |
| this.stop(); |
| this.onEndedCallback = null; |
| } |
| } |
|
|
| |
| |
| |
| export function createAudioPlayer(): AudioPlayer { |
| return new AudioPlayer(); |
| } |
|
|