Home > database >  Independent row buttons mat-table
Independent row buttons mat-table

Time:06-16

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:

<!-- audio display -->
<ng-container matColumnDef="isPlaying">
  <mat-header-cell *matHeaderCellDef>Playing</mat-header-cell>
  <mat-cell *matCellDef="let recording">
    <button mat-button (click)="playToggle(recording)" [disabled]="state?.error" *ngIf="!state?.playing">
      <mat-icon mat-list-icon>play_arrow</mat-icon>
    </button>
    <button mat-button (click)="pause()" *ngIf="state?.playing">
      <mat-icon mat-list-icon>pause</mat-icon>
    </button>
  </mat-cell>
</ng-container>

Audio Paused

Audio Playing

recordings.component.ts

@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 < Recording > ;
  currentUser: BehaviorSubject < UserModel > ;
  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 < any > ;

  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 => {
      this.state = state;
      console.log('this.state: '   JSON.stringify(this.state));
    });
  }

  public loadRecordings(): void {
    this._recordingsService.getRecordings(this.telephone).then(data => {
      this.recordings = data;
    });

    this._recordingsService.onRecordingClick
      .subscribe((recordings: Recording[]) => this.handleRecordings(recordings),
        err => 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("Problem loading contacts!");
  }

  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 => {

    });
  }

  pause(): void {
    this._recordingsService.pause();
  }

  play(): void {
    this._recordingsService.play();
  }

  stop(): void {
    this._recordingsService.stop();
  }
}

recordings.service.ts

@Injectable()
export class RecordingsService implements Resolve < any > {
  // Class variables
  routeParams: any;

  recordings: Recording[];
  onRecordingClick: BehaviorSubject < any > ;
  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 < RecordingStreamState > = 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 => {
      // 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) => {
        this.updateStateEvents(event);
        observer.next(event);
      };

      this.addEvents(this.audioObj, this.audioEvents, handler);
      return () => {
        // 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 => {
      obj.addEventListener(event, handler);
    });
  }

  private removeEvents(obj, events, handler): void {
    events.forEach(event => {
      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 < RecordingStreamState > {
    return this.stateChange.asObservable();
  }

  playStream(url): Observable < any > {
    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([]);
  }
}

CodePudding user response:

Looking at your code I can think of a probable solution. I don't know how the Recording object is defined, but if it contains some id it might work. Or you can use some other identifier.

As I said in the comment, state property is shared by all entries within your table. So it's not enough to determine the current playing record. Once it's true for one it's true for all of them. But maybe this can help:

<!-- audio display -->
<ng-container matColumnDef="isPlaying">
  <mat-header-cell *matHeaderCellDef>Playing</mat-header-cell>
  <mat-cell *matCellDef="let rowRecording">
    <button mat-button (click)="playToggle(rowRecording)" [disabled]="state?.error" *ngIf="!state?.playing">
      <mat-icon mat-list-icon>play_arrow</mat-icon>
    </button>
    <button mat-button (click)="pause()" *ngIf="state?.playing && rowRecording.id === recording.id">
      <mat-icon mat-list-icon>pause</mat-icon>
    </button>
  </mat-cell>
</ng-container>

As you can see, I renamed the recording within HTML to rowRecording to differentiate it from the one from the .ts code. Then I'm checking if the state is playing but also if the rowRecording.id equals the ID of assigned recording within .ts. You do it in the playToggle function.

But I would recommend extending the RecordingStreamState object with PlayingRecordId property which would be assigned when you press play. Then you can do someting like this (using your version of HTML)

<button 
  mat-button
  (click)="pause()"
  *ngIf="state?.playing && state?.playingRecordId === recording.id"
>
  • Related