Extract PlaybackInterface (#9526)
This commit is contained in:
parent
1e65dcd0aa
commit
9096bd82d6
8 changed files with 182 additions and 13 deletions
|
@ -32,6 +32,13 @@ export enum PlaybackState {
|
|||
Playing = "playing", // active progress through timeline
|
||||
}
|
||||
|
||||
export interface PlaybackInterface {
|
||||
readonly liveData: SimpleObservable<number[]>;
|
||||
readonly timeSeconds: number;
|
||||
readonly durationSeconds: number;
|
||||
skipTo(timeSeconds: number): Promise<void>;
|
||||
}
|
||||
|
||||
export const PLAYBACK_WAVEFORM_SAMPLES = 39;
|
||||
const THUMBNAIL_WAVEFORM_SAMPLES = 100; // arbitrary: [30,120]
|
||||
export const DEFAULT_WAVEFORM = arraySeed(0, PLAYBACK_WAVEFORM_SAMPLES);
|
||||
|
@ -45,7 +52,7 @@ function makePlaybackWaveform(input: number[]): number[] {
|
|||
return arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1);
|
||||
}
|
||||
|
||||
export class Playback extends EventEmitter implements IDestroyable {
|
||||
export class Playback extends EventEmitter implements IDestroyable, PlaybackInterface {
|
||||
/**
|
||||
* Stable waveform for representing a thumbnail of the media. Values are
|
||||
* guaranteed to be between zero and one, inclusive.
|
||||
|
@ -111,6 +118,18 @@ export class Playback extends EventEmitter implements IDestroyable {
|
|||
return this.currentState === PlaybackState.Playing;
|
||||
}
|
||||
|
||||
public get liveData(): SimpleObservable<number[]> {
|
||||
return this.clock.liveData;
|
||||
}
|
||||
|
||||
public get timeSeconds(): number {
|
||||
return this.clock.timeSeconds;
|
||||
}
|
||||
|
||||
public get durationSeconds(): number {
|
||||
return this.clock.durationSeconds;
|
||||
}
|
||||
|
||||
public emit(event: PlaybackState, ...args: any[]): boolean {
|
||||
this.state = event;
|
||||
super.emit(event, ...args);
|
||||
|
|
|
@ -23,6 +23,7 @@ import { _t } from "../../../languageHandler";
|
|||
import SeekBar from "./SeekBar";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
import { PlaybackState } from "../../../audio/Playback";
|
||||
|
||||
export default class AudioPlayer extends AudioPlayerBase {
|
||||
protected renderFileSize(): string {
|
||||
|
@ -61,7 +62,7 @@ export default class AudioPlayer extends AudioPlayerBase {
|
|||
<SeekBar
|
||||
playback={this.props.playback}
|
||||
tabIndex={-1} // prevent tabbing into the bar
|
||||
playbackPhase={this.state.playbackPhase}
|
||||
disabled={this.state.playbackPhase === PlaybackState.Decoding}
|
||||
ref={this.seekRef}
|
||||
/>
|
||||
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
||||
|
|
|
@ -21,6 +21,7 @@ import PlaybackClock from "./PlaybackClock";
|
|||
import AudioPlayerBase, { IProps as IAudioPlayerBaseProps } from "./AudioPlayerBase";
|
||||
import SeekBar from "./SeekBar";
|
||||
import PlaybackWaveform from "./PlaybackWaveform";
|
||||
import { PlaybackState } from "../../../audio/Playback";
|
||||
|
||||
export enum PlaybackLayout {
|
||||
/**
|
||||
|
@ -56,7 +57,7 @@ export default class RecordingPlayback extends AudioPlayerBase<IProps> {
|
|||
<SeekBar
|
||||
playback={this.props.playback}
|
||||
tabIndex={0} // allow keyboard users to fall into the seek bar
|
||||
playbackPhase={this.state.playbackPhase}
|
||||
disabled={this.state.playbackPhase === PlaybackState.Decoding}
|
||||
ref={this.seekRef}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -16,20 +16,20 @@ limitations under the License.
|
|||
|
||||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { PlaybackInterface } from "../../../audio/Playback";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
||||
interface IProps {
|
||||
// Playback instance to render. Cannot change during component lifecycle: create
|
||||
// an all-new component instead.
|
||||
playback: Playback;
|
||||
playback: PlaybackInterface;
|
||||
|
||||
// Tab index for the underlying component. Useful if the seek bar is in a managed state.
|
||||
// Defaults to zero.
|
||||
tabIndex?: number;
|
||||
|
||||
playbackPhase: PlaybackState;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -52,6 +52,7 @@ export default class SeekBar extends React.PureComponent<IProps, IState> {
|
|||
|
||||
public static defaultProps = {
|
||||
tabIndex: 0,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
constructor(props: IProps) {
|
||||
|
@ -62,26 +63,26 @@ export default class SeekBar extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
// We don't need to de-register: the class handles this for us internally
|
||||
this.props.playback.clockInfo.liveData.onUpdate(() => this.animationFrameFn.mark());
|
||||
this.props.playback.liveData.onUpdate(() => this.animationFrameFn.mark());
|
||||
}
|
||||
|
||||
private doUpdate() {
|
||||
this.setState({
|
||||
percentage: percentageOf(
|
||||
this.props.playback.clockInfo.timeSeconds,
|
||||
this.props.playback.timeSeconds,
|
||||
0,
|
||||
this.props.playback.clockInfo.durationSeconds),
|
||||
this.props.playback.durationSeconds),
|
||||
});
|
||||
}
|
||||
|
||||
public left() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds - ARROW_SKIP_SECONDS);
|
||||
this.props.playback.skipTo(this.props.playback.timeSeconds - ARROW_SKIP_SECONDS);
|
||||
}
|
||||
|
||||
public right() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds + ARROW_SKIP_SECONDS);
|
||||
this.props.playback.skipTo(this.props.playback.timeSeconds + ARROW_SKIP_SECONDS);
|
||||
}
|
||||
|
||||
private onChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -89,7 +90,7 @@ export default class SeekBar extends React.PureComponent<IProps, IState> {
|
|||
// change the value on the component. We can use this as a reliable "skip to X" function.
|
||||
//
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.clockInfo.durationSeconds);
|
||||
this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.durationSeconds);
|
||||
};
|
||||
|
||||
public render(): ReactNode {
|
||||
|
@ -105,7 +106,7 @@ export default class SeekBar extends React.PureComponent<IProps, IState> {
|
|||
value={this.state.percentage}
|
||||
step={0.001}
|
||||
style={{ '--fillTo': this.state.percentage } as ISeekCSS}
|
||||
disabled={this.props.playbackPhase === PlaybackState.Decoding}
|
||||
disabled={this.props.disabled}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue