方案
多个 url 连播
代码
eventBus
export class EventBus {
events: any;
constructor() {
this.events = Object.create(null);
}
on(eventName: string, handler: (args?: any) => void) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(handler);
}
once(eventName: string | string[], handler: (args?: any) => void) {
// eslint-disable-next-line no-param-reassign
handler.prototype.once = true;
if (Array.isArray(eventName)) {
eventName.forEach((event: string) => {
this.on(event, handler);
});
} else {
this.on(eventName, handler);
}
}
off(eventName: string, handler: (args?: any) => void) {
if (!this.events[eventName]) {
return;
}
const handlerIndex = this.events[eventName].findIndex((fn: () => void) => fn === handler);
this.events[eventName].splice(handlerIndex, 1);
if (this.events[eventName].length === 0) {
delete this.events[eventName];
}
}
emit(eventName: string, ...args: any[]) {
if (!this.events[eventName]) {
return;
}
this.events[eventName].forEach((handler: (args?: any) => void) => {
handler(...args);
if (handler.prototype?.once) {
this.off(eventName, handler);
}
});
}
}
audioPlayService
import { EventBus } from './EventBus';
export interface AutoPlayAudioServiceOptions {
onIndexChange?: (index: number) => void;
onPlayFinish?: () => void;
delay?: number;
onPlayError?: (error: ErrorEvent) => void;
onPlayingChange?: (isPlaying: boolean) => void;
}
const initState: AutoPlayAudioServiceOptions = {
onIndexChange: undefined,
onPlayFinish: undefined,
delay: 0,
onPlayError: undefined,
onPlayingChange: undefined,
};
export class AutoPlayAudioService implements AutoPlayAudioServiceOptions {
onIndexChange?: (index: number) => void;
onPlayFinish?: () => void;
onPlayError?: (error: ErrorEvent) => void;
onPlayingChange?: (isPlaying: boolean) => void;
constructor(options?: AutoPlayAudioServiceOptions) {
this.init(options);
}
public delay = 0;
public audioList: (HTMLAudioElement | AutoPlayAudioService)[] = [];
private canAddAudio = true;
private delayTimer: NodeJS.Timeout | null = null;
private eventBus = new EventBus();
private _curPlaryingIndex = -1;
get curPlaryingIndex() {
return this._curPlaryingIndex;
}
set curPlaryingIndex(index: number) {
this._curPlaryingIndex = index;
this.onIndexChange?.(index);
}
private _isPlaying = false;
set isPlaying(value) {
this._isPlaying = value;
this.onPlayingChange?.(value);
}
get isPlaying() {
return this._isPlaying;
}
private _loading = false;
set loading(val: boolean) {
this._loading = val;
}
get loading() {
return this._loading;
}
get curAudio() {
return this.audioList[this.curPlaryingIndex];
}
init(options?: AutoPlayAudioServiceOptions) {
Object.assign(this, initState, options);
}
onEnded(ev: Event) {
const audio = ev.target as HTMLAudioElement;
audio.removeEventListener('ended', this.bindEndListener);
if (this.curPlaryingIndex + 1 === this.audioList.length) {
this.isPlaying = false;
this.eventBus.emit('ended', ev);
this.onPlayFinish?.();
return;
}
if (this.delay) {
this.delayTimer = setTimeout(() => {
this.curPlaryingIndex += 1;
this.audioList[this.curPlaryingIndex].play();
}, this.delay);
} else {
this.curPlaryingIndex += 1;
this.audioList[this.curPlaryingIndex].play();
}
}
bindEndListener = this.onEnded.bind(this);
addAudio(audio: HTMLAudioElement | AutoPlayAudioService, autoPlay = true) {
if (!audio) {
return;
}
if (!this.canAddAudio) {
return;
}
audio.addEventListener('error', (e) => {
Logger.aliyunLog('[autoPlayAudio] error', e);
this.eventBus.emit('error', e);
this.isPlaying = false;
audio.removeEventListener('ended', this.bindEndListener);
if (this.curPlaryingIndex + 1 === this.audioList.length) {
this.onPlayError?.(e);
return;
}
this.curPlaryingIndex += 1;
this.audioList[this.curPlaryingIndex].play();
});
audio.addEventListener('ended', this.bindEndListener);
this.audioList.push(audio);
const canFirstPlay = this.curPlaryingIndex === -1 && !this.loading;
const canNextPlay = !this.isPlaying && !this.loading;
const canPlay = autoPlay && (canFirstPlay || canNextPlay);
if (!canPlay) {
audio.load();
return;
}
this.isPlaying = true;
this.loading = true;
this.curPlaryingIndex = this.curPlaryingIndex += 1;
return audio
.play()
.catch((error) => {
Logger.aliyunLog('[autoPlayAudio] playError', { error });
})
.finally(() => {
this.loading = false;
});
}
initAudioList(options?: AutoPlayAudioServiceOptions) {
this.init(options);
this.stopAudio();
this.audioList = [];
this.curPlaryingIndex = -1;
this.canAddAudio = true;
}
stopAudio() {
this.isPlaying = false;
if (this.delayTimer) {
clearTimeout(this.delayTimer);
this.delayTimer = null;
}
this.audioList.forEach((audio) => {
audio.pause();
audio.removeEventListener('ended', this.bindEndListener);
});
}
pause() {
if (!this.isPlaying) {
return;
}
this.isPlaying = false;
this.audioList[this.curPlaryingIndex]?.pause();
}
play() {
if (this.isPlaying) {
return;
}
if (this.curPlaryingIndex === -1) {
this.curPlaryingIndex = 0;
}
const audio = this.audioList[this.curPlaryingIndex];
if (!audio) {
return;
}
this.isPlaying = true;
return audio.play();
}
load() {
this.audioList.forEach((audio) => {
audio.load();
});
}
addEventListener(eventName: string, handler: (args?: any) => void) {
this.eventBus.on(eventName, handler);
}
removeEventListener(eventName: string, handler: (args?: any) => void) {
this.eventBus.off(eventName, handler);
}
}