title: Independent row buttons mat-table
body: <p>I have a mat-table where each row represents a audio recording for a call. At the end of each row, there is a play button which plays the respective audio file when clicked. However, when I click any play button, the correct audio file plays but all the play buttons change to stop buttons. Here is a snippet of the html:</p>
  <pre class="lang-html prettyprint-override"><code>&lt;!-- audio display --&gt;
  &lt;ng-container matColumnDef=&quot;isPlaying&quot;&gt;
    &lt;mat-header-cell *matHeaderCellDef&gt;Playing&lt;/mat-header-cell&gt;
    &lt;mat-cell *matCellDef=&quot;let recording&quot;&gt;
      &lt;button mat-button (click)=&quot;playToggle(recording)&quot; [disabled]=&quot;state?.error&quot; *ngIf=&quot;!state?.playing&quot;&gt;
        &lt;mat-icon mat-list-icon&gt;play_arrow&lt;/mat-icon&gt;
      &lt;/button&gt;
      &lt;button mat-button (click)=&quot;pause()&quot; *ngIf=&quot;state?.playing&quot;&gt;
        &lt;mat-icon mat-list-icon&gt;pause&lt;/mat-icon&gt;
      &lt;/button&gt;
    &lt;/mat-cell&gt;
  &lt;/ng-container&gt;
  </code></pre>
  <p><a href="https://i.stack.imgur.com/rghVI.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/rghVI.png" alt="Audio Paused" /></a></p>
  <p><a href="https://i.stack.imgur.com/oK4Yt.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/oK4Yt.png" alt="Audio Playing" /></a></p>
  <p>recordings.component.ts</p>
  <pre class="lang-typescript prettyprint-override"><code>@Component({
    selector: 'recordings',
    templateUrl: './recordings.component.html',
    styleUrls: ['./recordings.component.scss'],
    encapsulation: ViewEncapsulation.None,
    animations: fuseAnimations
  })
  export class RecordingsComponent implements OnInit {
  
    // Class Variables
    recordingPlaying = false;
    buttonValue = 'Play Recording';
    displayColumns: string[] = ['date', 'time'];
    dataSource: MatTableDataSource &lt; Recording &gt; ;
    currentUser: BehaviorSubject &lt; UserModel &gt; ;
    dataLoading = true;
    recordings: Recording[];
    recording: Recording;
    state: RecordingStreamState;
    currentFile: any = {};
  
    @ViewChild(MatPaginator) paginator: MatPaginator;
    @ViewChild(MatSort) sort: MatSort;
  
    @Input()
    telephone: string;
  
    // Private
    private audio = new Audio();
    private _unsubscribeAll: Subject &lt; any &gt; ;
  
    constructor(
      private _recordingsService: RecordingsService,
      private _usersService: UserService,
      private _matSnackBar: MatSnackBar,
      private _fuseSidebarService: FuseSidebarService,
      private _matDialog: MatDialog
    ) {
      // Set the defaults
      this._unsubscribeAll = new Subject();
    }
  
    ngOnInit(): void {
      this.currentUser = this._usersService.user;
      this.loadRecordings();
  
      // listen to stream state
      this._recordingsService.getState().subscribe(state =&gt; {
        this.state = state;
        console.log('this.state: ' + JSON.stringify(this.state));
      });
    }
  
    public loadRecordings(): void {
      this._recordingsService.getRecordings(this.telephone).then(data =&gt; {
        this.recordings = data;
      });
  
      this._recordingsService.onRecordingClick
        .subscribe((recordings: Recording[]) =&gt; this.handleRecordings(recordings),
          err =&gt; this.handleRecordingsError(err)
        );
    }
  
    private handleRecordings(recordings: Recording[]): void {
      this.dataLoading = false;
      this.dataSource = new MatTableDataSource(recordings);
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
    }
  
    private handleRecordingsError(err): void {
      console.error(err);
      // this.alertService.error(&quot;Problem loading contacts!&quot;);
    }
  
    playToggle(recording): void {
  
      // TODO: (remove) if this is the reocrding already playing, pause, else play
      if (recording === this.recording) {
        this.pause();
        this.recording = null;
      } else {
        this.recording = recording;
        console.log('this.recording: ' + JSON.stringify(this.recording));
  
        if (this.recording === undefined) {
          console.log('could not find recording.');
        } else {
          this.playStream(this.recording.url);
        }
      }
    }
  
    playStream(url): void {
      // subscribes to playStream in our service and starts listening to  media events
      // like canplay, playing etc. This should be done in the stateChange object
      // but we use this method to start the observable and audio playback
      this._recordingsService.playStream(url).subscribe(events =&gt; {
  
      });
    }
  
    pause(): void {
      this._recordingsService.pause();
    }
  
    play(): void {
      this._recordingsService.play();
    }
  
    stop(): void {
      this._recordingsService.stop();
    }
  }
  </code></pre>
  <p>recordings.service.ts</p>
  <pre class="lang-typescript prettyprint-override"><code>@Injectable()
  export class RecordingsService implements Resolve &lt; any &gt; {
    // Class variables
    routeParams: any;
  
    recordings: Recording[];
    onRecordingClick: BehaviorSubject &lt; any &gt; ;
    private stop$ = new Subject();
    private audioObj = new Audio();
    audioEvents = [
      'ended',
      'error',
      'play',
      'playing',
      'pause',
      'timeupdate',
      'canplay',
      'loadedmetadata',
      'loadstart',
    ];
  
    // gets from the interface we created
    private state: RecordingStreamState = {
      playing: false,
      readableCurrentTime: '',
      readableDuration: '',
      duration: undefined,
      currentTime: undefined,
      canplay: false,
      error: false,
    };
  
    private stateChange: BehaviorSubject &lt; RecordingStreamState &gt; = new BehaviorSubject(
      this.state
    );
  
    // tslint:disable-next-line:typedef
    private streamObservable(url) {
      console.log('in streamObservable in service');
  
      // tslint:disable-next-line:no-unused-expression
      return new Observable(observer =&gt; {
        // Play audio
        this.audioObj.src = url;
        this.audioObj.load();
        this.audioObj.play();
  
        // a way for the observer to react to items the observable emits
        const handler = (event: Event) =&gt; {
          this.updateStateEvents(event);
          observer.next(event);
        };
  
        this.addEvents(this.audioObj, this.audioEvents, handler);
        return () =&gt; {
          // stop playing
          this.audioObj.pause();
          this.audioObj.currentTime = 0;
          // remove event listeners
          this.removeEvents(this.audioObj, this.audioEvents, handler);
        };
      });
    }
  
    private addEvents(obj, events, handler): void {
      events.forEach(event =&gt; {
        obj.addEventListener(event, handler);
      });
    }
  
    private removeEvents(obj, events, handler): void {
      events.forEach(event =&gt; {
        obj.removeEventListener(event, handler);
      });
    }
  
  
    // how to handle the different events , recording: Recording
    private updateStateEvents(event: Event): void {
      console.log('event_type: ' + event.type);
      switch (event.type) {
        case 'canplay':
          this.state.duration = this.audioObj.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.audioObj.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(): void {
      this.state = {
        playing: false,
        readableCurrentTime: '',
        readableDuration: '',
        duration: undefined,
        currentTime: undefined,
        canplay: false,
        error: false,
      };
    }
  
    getState(): Observable &lt; RecordingStreamState &gt; {
      return this.stateChange.asObservable();
    }
  
    playStream(url): Observable &lt; any &gt; {
      return this.streamObservable(url).pipe(takeUntil(this.stop$));
    }
  
    play(): void {
      this.audioObj.play();
    }
  
    pause(): void {
      this.audioObj.pause();
    }
  
    stop(): void {
      this.stop$.next();
    }
  
    seekTo(seconds): void {
      this.audioObj.currentTime = seconds;
    }
  
    formatTime(time: number, format: string = 'HH:mm:ss'): string {
      const momentTime = time * 1000;
      return moment.utc(momentTime).format(format);
    }
  
    /**
     * Constructor
     *
     * @param {HttpClient} _httpClient
    */
    constructor(
      private _httpClient: HttpClient,
    ) {
      // Set the defaults
      this.onRecordingClick = new BehaviorSubject([]);
    }
  }
  </code></pre>