| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- // 使用 Howler.js 的音频播放器
- import { Howl } from 'howler'
- export interface AudioPlayerConfig {
- onPlay?: () => void
- onEnded?: () => void
- onError?: (error: Error) => void
- }
- export class HowlerAudioPlayer {
- private audioChunks: ArrayBuffer[] = []
- private playing = false
- private currentHowl: Howl | null = null
- private config: AudioPlayerConfig
- private audioReady = false
- constructor(config: AudioPlayerConfig = {}) {
- this.config = config
- }
- async init(): Promise<void> {
- console.log('使用 Howler.js 音频播放器')
- this.audioReady = true
- return Promise.resolve()
- }
- /**
- * 在用户交互时调用(激活音频)
- */
- prepareForPlayback() {
- if (this.audioReady) {
- console.log('🎯 Howler.js 已准备就绪')
- return
- }
-
- // Howler.js 不需要特殊激活,但可以预加载
- this.audioReady = true
- console.log('✅ Howler.js 音频播放器已激活')
- }
- appendAudioChunk(chunk: ArrayBuffer) {
- console.log(`📦 收集音频块: ${chunk.byteLength} bytes`)
- this.audioChunks.push(chunk)
- }
- async play() {
- if (this.audioChunks.length === 0 || this.playing) {
- console.log('⚠️ 没有音频数据或正在播放')
- return
- }
- console.log(`🎵 使用 Howler.js 播放音频,共 ${this.audioChunks.length} 块`)
- this.playing = true
- // 合并音频块
- const totalLength = this.audioChunks.reduce((sum, chunk) => sum + chunk.byteLength, 0)
- const combined = new Uint8Array(totalLength)
- let offset = 0
-
- for (const chunk of this.audioChunks) {
- combined.set(new Uint8Array(chunk), offset)
- offset += chunk.byteLength
- }
- // 检测格式
- let format = 'ogg' // 默认格式
- const firstByte = combined[0]
- if (firstByte === 0x4F && combined[1] === 0x67) {
- format = 'ogg'
- } else if (firstByte === 0x1A && combined[1] === 0x45) {
- format = 'webm'
- }
- console.log('检测到的音频格式:', format)
- // 创建 Blob URL
- const blob = new Blob([combined.buffer], {
- type: format === 'ogg' ? 'audio/ogg' : 'audio/webm'
- })
- const url = URL.createObjectURL(blob)
- // 清理旧的 Howl
- if (this.currentHowl) {
- this.currentHowl.unload()
- }
- // 创建新的 Howl 实例
- this.currentHowl = new Howl({
- src: [url],
- format: [format],
- html5: true, // 使用 HTML5 Audio(兼容性更好)
- volume: 1.0, // 最大音量
-
- onplay: () => {
- console.log('🔊 Howler.js 开始播放')
- this.config.onPlay?.()
- },
- onend: () => {
- console.log('✅ Howler.js 播放完成')
- URL.revokeObjectURL(url)
- this.playing = false
- this.audioChunks = [] // 清空缓存
- this.config.onEnded?.()
- },
- onloaderror: (id, error) => {
- console.error('❌ Howler.js 加载失败:', error)
- URL.revokeObjectURL(url)
- this.playing = false
- this.config.onError?.(new Error(`音频加载失败: ${error}`))
- },
- onplayerror: (id, error) => {
- console.error('❌ Howler.js 播放失败:', error)
- URL.revokeObjectURL(url)
- this.playing = false
- this.config.onError?.(new Error(`音频播放失败: ${error}`))
- },
- })
- // 开始播放
- this.currentHowl.play()
- }
- pause() {
- if (this.currentHowl) {
- this.currentHowl.pause()
- }
- }
- stop() {
- console.log('🛑 停止 Howler.js 播放')
- if (this.currentHowl) {
- this.currentHowl.stop()
- this.currentHowl.unload()
- this.currentHowl = null
- }
- this.audioChunks = []
- this.playing = false
- }
- clear() {
- console.log('🧹 清理 Howler.js 播放器状态')
- this.stop()
- }
- destroy() {
- this.stop()
- this.audioReady = false
- }
- isPlaying(): boolean {
- return this.playing
- }
- getCurrentTime(): number {
- return this.currentHowl?.seek() || 0
- }
- getBufferedProgress(): number {
- return this.audioChunks.length
- }
- /**
- * 设置音量(0.0-1.0)
- * 注意:Howler.js 的 volume 最大是 1.0,如需更大音量需要用 Web Audio API
- */
- setVolume(volume: number) {
- const clampedVolume = Math.max(0, Math.min(1.0, volume))
- if (this.currentHowl) {
- this.currentHowl.volume(clampedVolume)
- console.log('🔊 Howler.js 音量设置为:', clampedVolume)
- }
- }
- }
|