音频播放工具设计

技术标签:vue rxjs ts
支持功能:web音频播放\队列播放\插队播放\倍速播放\播放状态汇报等功能

直接上代码:

import { Subject } from 'rxjs'
export interface ISound {
  type: IAudioType
  url: string
  speed?: number
  uid?: number
  id?: string
  currentTime?: number
}
export type INormalSound = {
  id: string
  url: string
  speed?: number
  loop?: boolean
}
export enum IAudioType {
  PRELOAD = 'preload',
  OFFLINE_INTERPHONE = 'offlineInterphone',
  INTERPHONE = 'interphone',
  NORMAL = 'normal',
  MESSAGE = 'MESSAGE',
}
export enum IAudioStatus {
  BUSY = 'busy',
  LOADING = 'loading',
  LOADED = 'LOADED',
  PLAYING = 'playing',
  PAUSED = 'paused',
  COMPLETE = 'complete',
}
export interface IAudio {
  url: string
  type: IAudioType
  status: IAudioStatus
  id?: string
  uid?: number
  currentTime?: number
  totalTime?: number
  isPlayed?: boolean
}
type IStatusCallBack = (args: { id: string; status: IAudioStatus; progressPercent?: number; currentTime?: number; uid?: number }) => void
export interface IAudioPlayer {
  playNormal: (sound: INormalSound, statusCallBack?: IStatusCallBack, speed?: number) => void
  playRing: (sound: INormalSound) => void
  stopRing: () => void
  setNormalSpeed: (speed: number) => void
  pause: () => void
  addSound: (sound: ISound) => void
  setAudioVolume: (type: IAudioType, value: number) => void
  audioSubject: Subject
  influencePause: () => void
  play: () => void
  preload: (sound: INormalSound) => void
  messageAutoPlay: (sounds: INormalSound[]) => void
  stop: (types: IAudioType[]) => void
}
export class AudioPlayer {
  audioSubject = new Subject()
  audio!: HTMLAudioElement
  autoPlay = false
  curSound!: ISound | null
  status: IAudioStatus = IAudioStatus.COMPLETE
  mode!: IAudioType
  volumeMap: Map = new Map()
  speedMap: Map = new Map()
  sounds: Record = Object.create(null)
  statusCallBack!: IStatusCallBack
  constructor(args?: Record) {
    this.autoPlay = (args?.autoPlay || false) as boolean
    if (document.querySelector('#easonYuAudio')) {
      this.audio = document.querySelector('#easonYuAudio') as HTMLAudioElement
    } else {
      this.audio = document.createElement('audio')
      this.audio.id = 'easonYuAudio'
      document.body.appendChild(this.audio)
    }
    this.initEvent()
  }
  private initEvent(): void {
    this.audio.addEventListener('waiting', this.handleWaiting.bind(this), false)
    this.audio.addEventListener('loadedmetadata', this.handleLoaded.bind(this), false)
    this.audio.addEventListener('timeupdate', this.handleTimeUpdate.bind(this), false)
    this.audio.addEventListener('pause', this.handlePause.bind(this), false)
    this.audio.addEventListener('ended', this.handleEndPlay.bind(this), false)
  }
  toggleInterphoneMode(value: boolean): void {
    this.mode = value ? IAudioType.INTERPHONE : IAudioType.NORMAL
  }
  setAudioVolume(type: IAudioType, value: number): void {
    this.volumeMap.set(type, value)
  }
  setAudioSpeed(type: IAudioType, value: number): void {
    this.speedMap.set(type, value)
  }
  handleLoaded(): void {
    if (this.curSound) {
      if (typeof this.speedMap.get(this.curSound.type) != 'undefined') {
        this.audio.playbackRate = this.speedMap.get(this.curSound.type) as number
      } else if (this.curSound.type == IAudioType.OFFLINE_INTERPHONE) {
        this.audio.playbackRate = 1.5
      } else if (this.curSound.speed) {
        this.audio.playbackRate = this.curSound.speed
      } else {
        this.audio.playbackRate = 1.0
      }
    }
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.LOADED,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
      currentTime: 0,
      totalTime: this.audio.duration,
    })
  }
  handleTimeUpdate(): void {
    this.status = IAudioStatus.PLAYING
    if (typeof this.statusCallBack != 'undefined' && !this.audio.paused && this.curSound?.type == IAudioType.NORMAL) {
      const percent = Number((this.audio.currentTime / this.audio.duration).toFixed(2))
      if (!isNaN(percent)) {
        this.statusCallBack({
          id: this.curSound.id as string,
          status: IAudioStatus.PLAYING,
          progressPercent: percent,
          currentTime: this.audio.currentTime,
        })
      }
    }
    if (this.curSound) {
      if (typeof this.volumeMap.get(this.curSound.type) != 'undefined') {
        this.audio.volume = this.volumeMap.get(this.curSound.type) as number
      } else {
        this.audio.volume = 1.0
      }
    }
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.PLAYING,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
      currentTime: this.audio.currentTime,
      totalTime: this.audio.duration,
    })
  }
  handleWaiting(): void {
    this.status = IAudioStatus.LOADING
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.LOADING,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
    })
  }
  handlePause(): void {
    this.status = IAudioStatus.PAUSED
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.PAUSED,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
      currentTime: this.audio.currentTime,
      totalTime: this.audio.duration,
    })
  }
  play(): void {
    if ([IAudioStatus.COMPLETE, IAudioStatus.PAUSED].indexOf(this.status) == -1) {
      return
    }
    this.curSound = this.getNewSound()
    if (!this.curSound) {
      return
    }
    this.status = IAudioStatus.PLAYING
    this.audio.src = this.curSound.url
    if (typeof this.volumeMap.get(this.curSound.type) != 'undefined') {
      this.audio.volume = this.volumeMap.get(this.curSound.type) as number
    } else {
      this.audio.volume = 1.0
    }
    if (typeof this.speedMap.get(this.curSound.type) != 'undefined') {
      this.audio.playbackRate = this.speedMap.get(this.curSound.type) as number
    } else {
      this.audio.playbackRate = 1.0
    }
    if (this.curSound.currentTime) {
      this.audio.currentTime = this.curSound.currentTime
    }
    this.audio.play()
  }
  handleEndPlay(): void {
    this.status = IAudioStatus.COMPLETE
    if (this.statusCallBack && this.curSound?.type == IAudioType.NORMAL) {
      this.statusCallBack({
        id: this.curSound.id as string,
        status: IAudioStatus.COMPLETE,
      })
    }
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.COMPLETE,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
      currentTime: this.audio.currentTime,
      totalTime: this.audio.duration,
    })
    if (this.autoPlay) {
      this.play()
    }
  }
  setNormalSpeed(speed: number): void {
    this.setAudioSpeed(IAudioType.NORMAL, speed)
    this.audio.playbackRate = speed
  }
  jumpInLine(): void {
    if (
      this.curSound &&
      this.status == IAudioStatus.PLAYING &&
      [IAudioType.INTERPHONE, IAudioType.OFFLINE_INTERPHONE].indexOf(this.curSound?.type) != -1
    ) {
      this.addSound({ ...this.curSound, currentTime: this.audio.currentTime }, true)
    }
  }
  messageAutoPlay(sounds: INormalSound[]): void {
    this.sounds[IAudioType.MESSAGE] = sounds.map((item) => {
      return {
        ...item,
        type: IAudioType.MESSAGE,
      }
    })
    this.play()
  }
  playNormal(sound: INormalSound, statusCallBack?: IStatusCallBack, speed = 1): void {
    if (this.curSound?.type == IAudioType.NORMAL) {
      this.pause()
    }
    this.jumpInLine()
    if (statusCallBack) {
      this.statusCallBack = statusCallBack
    }
    if (sound.speed) {
      this.setNormalSpeed(sound.speed)
    }
    if (speed != 1) {
      this.setNormalSpeed(speed)
    }
    if (sound.loop) {
      this.audio.loop = true
    } else {
      this.audio.loop = false
    }
    if (this.curSound && this.curSound.id == sound.id) {
      this.audio.play()
    } else {
      this.curSound = {
        ...sound,
        type: IAudioType.NORMAL,
      }
      this.audio.src = sound.url
      this.audio.play()
    }
  }
  playRing(sound: INormalSound): void {
    this.influencePause()
    sound.loop = true
    sound.speed = 1
    this.playNormal(sound)
  }
  stopRing(): void {
    this.audio.pause()
    this.audio.loop = false
    this.status = IAudioStatus.COMPLETE
  }
  pause(): void {
    this.status = IAudioStatus.PAUSED
    this.audioSubject.next({
      url: this.curSound?.url as string,
      type: this.curSound?.type as IAudioType,
      status: IAudioStatus.PAUSED,
      id: this.curSound?.id || '',
      uid: this.curSound?.uid || 0,
      currentTime: this.audio.currentTime,
      totalTime: this.audio.duration,
    })
    this.audio.pause()
  }
  addSound(sound: ISound, first?: boolean): void {
    if (!this.sounds[sound.type]) {
      // all type audio set initial
      this.sounds[sound.type] = []
    }
    if (first) {
      this.sounds[sound.type].unshift(sound)
    } else {
      this.sounds[sound.type].push(sound)
    }
    if (this.autoPlay) {
      this.play()
    }
  }
  getNewSound(): ISound | null {
    const messageSound = this.getMessageSound()
    if (messageSound) {
      return messageSound
    }
    const offlineInterphoneSound = this.getOfflineInterPhoneSound()
    if (offlineInterphoneSound) {
      return offlineInterphoneSound
    }
    const interphoneSound = this.getInterPhoneSound()
    if (interphoneSound) {
      return interphoneSound
    }
    return null
  }
  getMessageSound(): ISound | null {
    if (this.sounds[IAudioType.MESSAGE] && this.sounds[IAudioType.MESSAGE].length) {
      return this.sounds[IAudioType.MESSAGE].shift() as ISound
    }
    return null
  }
  getOfflineInterPhoneSound(): ISound | null {
    if (this.sounds[IAudioType.OFFLINE_INTERPHONE] && this.sounds[IAudioType.OFFLINE_INTERPHONE].length) {
      return this.sounds[IAudioType.OFFLINE_INTERPHONE].shift() as ISound
    }
    return null
  }
  getInterPhoneSound(): ISound | null {
    if (this.sounds[IAudioType.INTERPHONE] && this.sounds[IAudioType.INTERPHONE].length) {
      return this.sounds[IAudioType.INTERPHONE].shift() as ISound
    }
    return null
  }
  getNormalSound(id?: string): ISound | null {
    if (this.sounds[IAudioType.NORMAL] && this.sounds[IAudioType.NORMAL].length) {
      if (id) {
        const findIndex = this.sounds[IAudioType.NORMAL].findIndex((item) => item.id == id)
        if (findIndex > -1) {
          return this.sounds[IAudioType.NORMAL].splice(findIndex, 1)[0]
        }
      } else {
        return this.sounds[IAudioType.NORMAL].shift() as ISound
      }
    }
    return null
  }
  influencePause(): void {
    this.jumpInLine()
    this.pause()
  }
  preload(sound: INormalSound): void {
    const audio = new Audio()
    audio.autoplay = false
    audio.addEventListener(
      'loadedmetadata',
      () => {
        this.audioSubject.next({
          id: sound.id,
          url: sound.url,
          type: IAudioType.PRELOAD,
          status: IAudioStatus.LOADED,
          totalTime: audio.duration,
          currentTime: audio.currentTime,
        })
      },
      false
    )
    audio.src = sound.url
  }
  stop(types: IAudioType[]): void {
    this.audio.pause()
    types.forEach((type) => {
      this.sounds[type] = []
    })
  }
}

你可能感兴趣的:(音频播放工具设计)