【问题标题】:Independent row buttons mat-table独立行按钮垫表
【发布时间】:2022-06-11 08:07:53
【问题描述】:

我有一个垫子表,其中每一行代表一个通话的录音。在每一行的末尾,有一个播放按钮,单击时播放相应的音频文件。但是,当我单击任何播放按钮时,会播放正确的音频文件,但所有播放按钮都会变为停止按钮。这是html的sn-p:

<!-- 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>

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([]);
  }
}

【问题讨论】:

  • 看起来state 被所有行共享。可以加个.ts代码吗?
  • 我在上面添加了组件和服务的ts文件。这是我第一次问关于全栈代码的问题,所以我提前道歉。
  • 不用担心,如果没有 ts 代码,很难假设问题

标签: html angular button audio


【解决方案1】:

查看您的代码,我可以想到一个可能的解决方案。我不知道Recording 对象是如何定义的,但如果它包含一些id 它可能会起作用。或者您可以使用其他标识符。

正如我在评论中所说,state 属性由表中的所有条目共享。所以仅仅确定当前的播放记录是不够的。一旦它对一个人是正确的,它对他们所有人都是正确的。但也许这会有所帮助:

<!-- 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>

如您所见,我将 HTML 中的 recording 重命名为 rowRecording 以区别于 .ts 代码中的那个。然后我正在检查状态是否正在播放,以及rowRecording.id 是否等于 .ts 中分配的记录的 ID。您可以在 playToggle 函数中执行此操作。

但我建议使用PlayingRecordId 属性扩展RecordingStreamState 对象,该属性将在您按下播放时分配。然后你可以做这样的事情(使用你的 HTML 版本)

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

【讨论】:

    猜你喜欢
    • 2020-08-18
    • 2013-12-10
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多