技术标签: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] = []
})
}
}