I am trying to create a reusable audio player component that I can use throughout my project. I followed this tutorial in creating the audio player itself.

现在的问题是,我决定让音频播放器自己成为一个可以在其他组件中呈现的组件,然后使用@Input传递音频URL。它可以与该组件的一个实例正常工作,但是一旦创建了该组件的另一个实例,该组件的两个渲染实例的行为相同,它们播放相同的文件,一个组件上的按钮会影响另一个组件,即,如果我按一个音频播放器上的播放按钮,第二个也将使用相同的文件播放。

这是相关的代码。

audio.service.ts

import { Injectable } from '@angular/core';
import { Subject, Observable, BehaviorSubject } from 'rxjs';
import { StreamState } from './stream-state';
import { takeUntil } from 'rxjs/operators';
import * as moment from 'moment';

@Injectable({
  providedIn: 'root',
})
export class AudioService {
  constructor() {}

  private stop$ = new Subject();
  private audioObject: HTMLAudioElement = new Audio();

  private audioEvents: Array<string> = [
    'ended',
    'error',
    'play',
    'playing',
    'pause',
    'timeupdate',
    'canplay',
    'loadedmetadata',
    'loadstart',
  ];
  private state: StreamState = {
    playing: false,
    readableCurrentTime: '',
    readableDuration: '',
    duration: undefined,
    currentTime: undefined,
    canplay: false,
    error: false,
    mute: false,
  };
  private streamObservable(url) {
    return new Observable(observer => {
      // Play audio
      this.audioObject.src = url;
      this.audioObject.load();
      this.audioObject.play();

      const handler = (event: Event) => {
        this.updateStateEvents(event);
        observer.next(event);
      };
      this.addEvents(this.audioObject, this.audioEvents, handler);
      return () => {
        this.audioObject.pause();
        this.audioObject.currentTime = 0;

        this.removeEvent(this.audioObject, this.audioEvents, handler);
        this.resetState();
      };
    });
  }
  private addEvents(obj, events, handler) {
    events.forEach(event => {
      obj.addEventListener(event, handler);
    });
  }

  private removeEvent(obj, events, handler) {
    events.forEach(event => {
      obj.removeEventListener(event, handler);
    });
  }

  playStream(url) {
    return this.streamObservable(url).pipe(takeUntil(this.stop$));
  }

  play() {
    this.audioObject.play();
  }
  pause() {
    this.audioObject.pause();
  }
  stop() {
    this.stop$.next();
  }
  mute() {
    this.audioObject.volume = 0;
    this.state.mute = true;
  }
  unmute() {
    this.audioObject.volume = 1;
    this.state.mute = false;
  }
  seekTo(seconds) {
    this.audioObject.currentTime = seconds;
  }

  formatTime(time: number, format: string = 'mm:ss') {
    const momentTime = time * 1000;
    return moment.utc(momentTime).format(format);
  }

  private stateChange: BehaviorSubject<StreamState> = new BehaviorSubject(this.state);

  private updateStateEvents(event: Event): void {
    switch (event.type) {
      case 'canplay':
        this.state.duration = this.audioObject.duration;
        this.state.readableDuration = this.formatTime(this.state.duration);
        this.state.canplay = true;
        break;

      case 'playing':
        this.state.playing = true;
        break;

      case 'pause':
        this.state.playing = false;
        break;

      case 'timeupdate':
        this.state.currentTime = this.audioObject.currentTime;
        this.state.readableCurrentTime = this.formatTime(this.state.currentTime);
        break;

      case 'error':
        this.resetState();
        this.state.error = true;
        break;
    }
    this.stateChange.next(this.state);
  }

  private resetState() {
    this.state = {
      playing: false,
      readableCurrentTime: '',
      readableDuration: '',
      duration: undefined,
      currentTime: undefined,
      canplay: false,
      error: false,
      mute: false,
    };
  }

  getState(): Observable<StreamState> {
    return this.stateChange.asObservable();
  }

audioplayer.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { StreamState } from './stream-state';
import { AudioService } from './audio.service';

@Component({
  selector: 'app-audio-player',
  templateUrl: './audio-player.component.html',
  styleUrls: ['./audio-player.component.scss'],
})
export class AudioPlayerComponent implements OnInit {
  @Input() audioUrl: string;
  @Input() ID: string;

  state: StreamState;

  constructor(private audioService: AudioService) {
    this.audioService.getState().subscribe(state => {
      this.state = state;
    });
  }

  ngOnInit(): void {
    console.log(this.audioUrl);
    this.playStream(this.audioUrl);
    this.pause();
  }
  playStream(url) {
    this.audioService.playStream(url).subscribe(events => {
      // console.log(events);
    });
  }

  pause() {
    this.audioService.pause();
  }
  play() {
    this.audioService.play();
  }
  stop() {
    this.audioService.stop();
  }
  mute() {
    this.audioService.mute();
  }
  unmute() {
    this.audioService.unmute();
  }
  onSliderChangeEnd(change) {
    this.audioService.seekTo(change.value);
  }
}

流状态

export interface StreamState {
  playing: boolean;
  readableCurrentTime: string;
  readableDuration: string;
  duration: number | undefined;
  currentTime: number | undefined;
  canplay: boolean;
  error: boolean;
  mute: boolean;
}

example.component.html

<div>
<app-audio-player audioUrl="audiofile.mp3"></app-audio-player>
<app-audio-player audioUrl="newaudio.mp3"></app-audio-player>
</div>

请采取什么措施以确保audioplayer组件的每个实例都是独立的。 谢谢。