title: How to create a reuseable audio player component with angular
body: <p>I am trying to create a reusable audio player component that I can use throughout my project. I followed this <a href="https://auth0.com/blog/building-an-audio-player-app-with-angular-and-rxjs/" rel="nofollow noreferrer">tutorial</a> in creating the audio player itself.</p>
  <p>Now the problem is that I decided to make the audio player a component on its own that can be rendered in other components and then pass the audio url using @Input. It works fine with one instance of the component, but once another instance of the component is created, both rendered instances of the component behaves the same, they play the same file, the button on one component affects the other component i.e if I press the play button on one audio player, the second one will also play with the same file.</p>
  <p>Here are the relevant code.</p>
  <p>audio.service.ts</p>
  <pre><code>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&lt;string&gt; = [
      '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 =&gt; {
        // Play audio
        this.audioObject.src = url;
        this.audioObject.load();
        this.audioObject.play();
  
        const handler = (event: Event) =&gt; {
          this.updateStateEvents(event);
          observer.next(event);
        };
        this.addEvents(this.audioObject, this.audioEvents, handler);
        return () =&gt; {
          this.audioObject.pause();
          this.audioObject.currentTime = 0;
  
          this.removeEvent(this.audioObject, this.audioEvents, handler);
          this.resetState();
        };
      });
    }
    private addEvents(obj, events, handler) {
      events.forEach(event =&gt; {
        obj.addEventListener(event, handler);
      });
    }
  
    private removeEvent(obj, events, handler) {
      events.forEach(event =&gt; {
        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&lt;StreamState&gt; = 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&lt;StreamState&gt; {
      return this.stateChange.asObservable();
    }
  </code></pre>
  <p>audioplayer.component.ts</p>
  <pre><code>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 =&gt; {
        this.state = state;
      });
    }
  
    ngOnInit(): void {
      console.log(this.audioUrl);
      this.playStream(this.audioUrl);
      this.pause();
    }
    playStream(url) {
      this.audioService.playStream(url).subscribe(events =&gt; {
        // 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);
    }
  }
  </code></pre>
  <p>stream-state.ts</p>
  <pre><code>export interface StreamState {
    playing: boolean;
    readableCurrentTime: string;
    readableDuration: string;
    duration: number | undefined;
    currentTime: number | undefined;
    canplay: boolean;
    error: boolean;
    mute: boolean;
  }
  </code></pre>
  <p>example.component.html</p>
  <pre><code>&lt;div&gt;
  &lt;app-audio-player audioUrl=&quot;audiofile.mp3&quot;&gt;&lt;/app-audio-player&gt;
  &lt;app-audio-player audioUrl=&quot;newaudio.mp3&quot;&gt;&lt;/app-audio-player&gt;
  &lt;/div&gt;
  
  </code></pre>
  <p>Please what can I do to make sure that each instance of the audioplayer component is independent.
  Thank you.</p>
  <p>Update.
  This is the link <a href="https://stackblitz.com/edit/angular-ivy-jfhr7x?file=src%2Fapp%2Fapp.component.html" rel="nofollow noreferrer">here</a> to the stackblitz</p>