move live recording logic down the component tree

pull/21833/head
Germain Souquet 2021-06-24 09:58:11 +01:00
parent 56467485f5
commit 21caa6df12
4 changed files with 115 additions and 13 deletions

View File

@ -227,8 +227,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
// only other UI is the recording-in-progress UI // only other UI is the recording-in-progress UI
return <div className="mx_VoiceMessagePrimaryContainer mx_VoiceRecordComposerTile_recording"> return <div className="mx_VoiceMessagePrimaryContainer mx_VoiceRecordComposerTile_recording">
<LiveRecordingClock seconds={this.state.seconds} /> <LiveRecordingClock recorder={this.state.recorder} />
<LiveRecordingWaveform relHeights={this.state.relHeights} /> <LiveRecordingWaveform recorder={this.state.recorder} />
</div>; </div>;
} }

View File

@ -12,15 +12,62 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import Clock, { IProps as IClockProps } from "./Clock"; import Clock from "./Clock";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import {
IRecordingUpdate,
VoiceRecording,
} from "../../../voice/VoiceRecording";
interface IProps {
recorder?: VoiceRecording;
}
interface IState {
seconds: number;
}
/** /**
* A clock for a live recording. * A clock for a live recording.
*/ */
@replaceableComponent("views.voice_messages.LiveRecordingClock") @replaceableComponent("views.voice_messages.LiveRecordingClock")
export default class LiveRecordingClock extends React.PureComponent<IClockProps> { export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
private seconds = 0;
private rafId: number;
state = {
seconds: 0,
}
componentDidMount() {
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
this.seconds = update.timeSeconds;
this.scheduledUpdate.mark();
});
}
private scheduledUpdate = new MarkedExecution(
() => this.updateClock(),
() => this.onLiveDataUpdate(),
)
private onLiveDataUpdate() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
this.rafId = requestAnimationFrame(() => this.scheduledUpdate.trigger())
}
private updateClock() {
this.setState({
seconds: this.seconds,
})
this.rafId = null;
}
public render() { public render() {
return <Clock seconds={this.props.seconds} />; return <Clock seconds={this.state.seconds} />;
} }
} }

View File

@ -12,18 +12,66 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import Waveform, { IProps as IWaveformProps } from "./Waveform"; import Waveform from "./Waveform";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import {
IRecordingUpdate,
VoiceRecording,
} from "../../../voice/VoiceRecording";
interface IProps {
recorder?: VoiceRecording;
}
interface IState {
waveform: number[]
}
/** /**
* A waveform which shows the waveform of a live recording * A waveform which shows the waveform of a live recording
*/ */
@replaceableComponent("views.voice_messages.LiveRecordingWaveform") @replaceableComponent("views.voice_messages.LiveRecordingWaveform")
export default class LiveRecordingWaveform extends React.PureComponent<IWaveformProps> { export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
public static defaultProps = { public static defaultProps = {
progress: 1, progress: 1,
}; };
private waveform: number[] = [];
private rafId: number;
state = {
waveform: [],
}
componentDidMount() {
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
this.waveform = update.waveform;
this.scheduledUpdate.mark();
});
}
private scheduledUpdate = new MarkedExecution(
() => this.updateWaveform(),
() => this.onLiveDataUpdate(),
)
private onLiveDataUpdate() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
this.rafId = requestAnimationFrame(() => this.scheduledUpdate.trigger())
}
private updateWaveform() {
this.setState({
waveform: this.waveform,
})
this.rafId = null;
}
public render() { public render() {
return <Waveform relHeights={this.props.relHeights} />; return <Waveform relHeights={this.state.waveform} />;
} }
} }

View File

@ -22,7 +22,7 @@ limitations under the License.
* The function starts unmarked. * The function starts unmarked.
*/ */
export class MarkedExecution { export class MarkedExecution {
private marked = false; private _marked = false;
/** /**
* Creates a MarkedExecution for the provided function. * Creates a MarkedExecution for the provided function.
@ -33,26 +33,33 @@ export class MarkedExecution {
constructor(private fn: () => void, private onMarkCallback?: () => void) { constructor(private fn: () => void, private onMarkCallback?: () => void) {
} }
/**
* Getter for the _marked property
*/
public get marked() {
return this._marked;
}
/** /**
* Resets the mark without calling the function. * Resets the mark without calling the function.
*/ */
public reset() { public reset() {
this.marked = false; this._marked = false;
} }
/** /**
* Marks the function to be called upon trigger(). * Marks the function to be called upon trigger().
*/ */
public mark() { public mark() {
if (!this.marked) this.onMarkCallback?.(); if (!this._marked) this.onMarkCallback?.();
this.marked = true; this._marked = true;
} }
/** /**
* If marked, the function will be called, otherwise this does nothing. * If marked, the function will be called, otherwise this does nothing.
*/ */
public trigger() { public trigger() {
if (!this.marked) return; if (!this._marked) return;
this.reset(); // reset first just in case the fn() causes a trigger() this.reset(); // reset first just in case the fn() causes a trigger()
this.fn(); this.fn();
} }