From 27d255f30e1fd023bc6a2f18bf368e007d83989a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 22 Jun 2021 12:27:54 +0100 Subject: [PATCH 1/8] Reduce audio waveform layout trashing --- .../views/voice_messages/_PlaybackContainer.scss | 4 ++++ src/components/views/voice_messages/Waveform.tsx | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/res/css/views/voice_messages/_PlaybackContainer.scss b/res/css/views/voice_messages/_PlaybackContainer.scss index 20def16d6a..944378fb19 100644 --- a/res/css/views/voice_messages/_PlaybackContainer.scss +++ b/res/css/views/voice_messages/_PlaybackContainer.scss @@ -33,9 +33,13 @@ limitations under the License. font-size: $font-14px; line-height: $font-24px; + contain: content; + .mx_Waveform { .mx_Waveform_bar { background-color: $voice-record-waveform-incomplete-fg-color; + height: 100%; + transform: scaleY(max(0.05, var(--barHeight))); &.mx_Waveform_bar_100pct { // Small animation to remove the mechanical feel of progress diff --git a/src/components/views/voice_messages/Waveform.tsx b/src/components/views/voice_messages/Waveform.tsx index 840a5a12b3..ae880933e6 100644 --- a/src/components/views/voice_messages/Waveform.tsx +++ b/src/components/views/voice_messages/Waveform.tsx @@ -34,16 +34,19 @@ interface IState { * For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be * "filled", as a demonstration of the progress property. */ + +import { CSSProperties } from "react"; + +export interface WaveformCSSProperties extends CSSProperties { + '--barHeight': number; +} + @replaceableComponent("views.voice_messages.Waveform") export default class Waveform extends React.PureComponent { public static defaultProps = { progress: 1, }; - public constructor(props) { - super(props); - } - public render() { return
{this.props.relHeights.map((h, i) => { @@ -53,7 +56,9 @@ export default class Waveform extends React.PureComponent { 'mx_Waveform_bar': true, 'mx_Waveform_bar_100pct': isCompleteBar, }); - return ; + return ; })}
; } From a85c6c67e059a92b4c57dda42cd88217cb3b3c3f Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 22 Jun 2021 12:28:23 +0100 Subject: [PATCH 2/8] Make waveform update match the screen refresh rate --- .../views/rooms/VoiceRecordComposerTile.tsx | 69 ++++++++++++++++--- .../voice_messages/LiveRecordingClock.tsx | 49 ------------- .../voice_messages/LiveRecordingWaveform.tsx | 60 ---------------- 3 files changed, 59 insertions(+), 119 deletions(-) delete mode 100644 src/components/views/voice_messages/LiveRecordingClock.tsx delete mode 100644 src/components/views/voice_messages/LiveRecordingWaveform.tsx diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 20d8c9c5d4..6fe6a5ab1c 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -15,19 +15,26 @@ limitations under the License. */ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import {_t} from "../../../languageHandler"; +import { _t } from "../../../languageHandler"; import React, {ReactNode} from "react"; -import {RecordingState, VoiceRecording} from "../../../voice/VoiceRecording"; +import { + IRecordingUpdate, + RECORDING_PLAYBACK_SAMPLES, + RecordingState, + VoiceRecording, +} from "../../../voice/VoiceRecording"; import {Room} from "matrix-js-sdk/src/models/room"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; import classNames from "classnames"; -import LiveRecordingWaveform from "../voice_messages/LiveRecordingWaveform"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import LiveRecordingClock from "../voice_messages/LiveRecordingClock"; -import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore"; +import Waveform from "../voice_messages/Waveform"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { arrayFastResample, arraySeed } from "../../../utils/arrays"; +import { percentageOf } from "../../../utils/numbers"; +import Clock from "../voice_messages/Clock"; +import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import RecordingPlayback from "../voice_messages/RecordingPlayback"; -import {MsgType} from "matrix-js-sdk/src/@types/event"; +import { MsgType } from "matrix-js-sdk/src/@types/event"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import CallMediaHandler from "../../../CallMediaHandler"; @@ -39,6 +46,8 @@ interface IProps { interface IState { recorder?: VoiceRecording; recordingPhase?: RecordingState; + relHeights: number[]; + seconds: number; } /** @@ -46,18 +55,58 @@ interface IState { */ @replaceableComponent("views.rooms.VoiceRecordComposerTile") export default class VoiceRecordComposerTile extends React.PureComponent { + private waveform: number[] = []; + private seconds = 0; + private scheduledAnimationFrame = false; + public constructor(props) { super(props); this.state = { recorder: null, // no recording started by default + seconds: 0, + relHeights: arraySeed(0, RECORDING_PLAYBACK_SAMPLES), }; } + public componentDidUpdate(prevProps, prevState) { + if (!prevState.recorder && this.state.recorder) { + this.state.recorder.liveData.onUpdate(this.onRecordingUpdate); + } + } + public async componentWillUnmount() { await VoiceRecordingStore.instance.disposeRecording(); } + private onRecordingUpdate = (update: IRecordingUpdate): void => { + this.waveform = update.waveform; + this.seconds = update.timeSeconds; + + if (this.scheduledAnimationFrame) { + return; + } + + this.scheduledAnimationFrame = true; + // The audio recorder flushes data faster than the screen refresh rate + // Using requestAnimationFrame makes sure that we only flush the data + // to react once per tick to avoid unneeded work. + requestAnimationFrame(() => { + // The waveform and the downsample target are pretty close, so we should be fine to + // do this, despite the docs on arrayFastResample. + const bars = arrayFastResample(Array.from(this.waveform), RECORDING_PLAYBACK_SAMPLES); + this.setState({ + // The incoming data is between zero and one, but typically even screaming into a + // microphone won't send you over 0.6, so we artificially adjust the gain for the + // waveform. This results in a slightly more cinematic/animated waveform for the + // user. + relHeights: bars.map(b => percentageOf(b, 0, 0.50)), + seconds: this.seconds, + }); + this.scheduledAnimationFrame = false; + }); + } + // called by composer public async send() { if (!this.state.recorder) { @@ -178,8 +227,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent - - + + ; } diff --git a/src/components/views/voice_messages/LiveRecordingClock.tsx b/src/components/views/voice_messages/LiveRecordingClock.tsx deleted file mode 100644 index b82539eb16..0000000000 --- a/src/components/views/voice_messages/LiveRecordingClock.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import {IRecordingUpdate, VoiceRecording} from "../../../voice/VoiceRecording"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import Clock from "./Clock"; - -interface IProps { - recorder: VoiceRecording; -} - -interface IState { - seconds: number; -} - -/** - * A clock for a live recording. - */ -@replaceableComponent("views.voice_messages.LiveRecordingClock") -export default class LiveRecordingClock extends React.PureComponent { - public constructor(props) { - super(props); - - this.state = {seconds: 0}; - this.props.recorder.liveData.onUpdate(this.onRecordingUpdate); - } - - private onRecordingUpdate = (update: IRecordingUpdate) => { - this.setState({seconds: update.timeSeconds}); - }; - - public render() { - return ; - } -} diff --git a/src/components/views/voice_messages/LiveRecordingWaveform.tsx b/src/components/views/voice_messages/LiveRecordingWaveform.tsx deleted file mode 100644 index aab89f6ab1..0000000000 --- a/src/components/views/voice_messages/LiveRecordingWaveform.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import {IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording} from "../../../voice/VoiceRecording"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {arrayFastResample, arraySeed} from "../../../utils/arrays"; -import {percentageOf} from "../../../utils/numbers"; -import Waveform from "./Waveform"; - -interface IProps { - recorder: VoiceRecording; -} - -interface IState { - heights: number[]; -} - -/** - * A waveform which shows the waveform of a live recording - */ -@replaceableComponent("views.voice_messages.LiveRecordingWaveform") -export default class LiveRecordingWaveform extends React.PureComponent { - public constructor(props) { - super(props); - - this.state = {heights: arraySeed(0, RECORDING_PLAYBACK_SAMPLES)}; - this.props.recorder.liveData.onUpdate(this.onRecordingUpdate); - } - - private onRecordingUpdate = (update: IRecordingUpdate) => { - // The waveform and the downsample target are pretty close, so we should be fine to - // do this, despite the docs on arrayFastResample. - const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); - this.setState({ - // The incoming data is between zero and one, but typically even screaming into a - // microphone won't send you over 0.6, so we artificially adjust the gain for the - // waveform. This results in a slightly more cinematic/animated waveform for the - // user. - heights: bars.map(b => percentageOf(b, 0, 0.50)), - }); - }; - - public render() { - return ; - } -} From a6367c079620d95141ddcd45c99952d250818568 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 23 Jun 2021 09:33:38 +0100 Subject: [PATCH 3/8] Reintroduce LiveRecording components for maintenance reasons --- .../views/rooms/VoiceRecordComposerTile.tsx | 8 ++--- src/components/views/voice_messages/Clock.tsx | 4 +-- .../voice_messages/LiveRecordingClock.tsx | 26 +++++++++++++++++ .../voice_messages/LiveRecordingWaveform.tsx | 29 +++++++++++++++++++ .../views/voice_messages/Waveform.tsx | 4 +-- 5 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/components/views/voice_messages/LiveRecordingClock.tsx create mode 100644 src/components/views/voice_messages/LiveRecordingWaveform.tsx diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 6fe6a5ab1c..5868eed02d 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -26,11 +26,11 @@ import { import {Room} from "matrix-js-sdk/src/models/room"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import classNames from "classnames"; -import Waveform from "../voice_messages/Waveform"; +import LiveRecordingWaveform from "../voice_messages/LiveRecordingWaveform"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { arrayFastResample, arraySeed } from "../../../utils/arrays"; import { percentageOf } from "../../../utils/numbers"; -import Clock from "../voice_messages/Clock"; +import LiveRecordingClock from "../voice_messages/LiveRecordingClock"; import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import RecordingPlayback from "../voice_messages/RecordingPlayback"; @@ -227,8 +227,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent - - + + ; } diff --git a/src/components/views/voice_messages/Clock.tsx b/src/components/views/voice_messages/Clock.tsx index 23e6762c52..1e78cc7bbd 100644 --- a/src/components/views/voice_messages/Clock.tsx +++ b/src/components/views/voice_messages/Clock.tsx @@ -15,9 +15,9 @@ limitations under the License. */ import React from "react"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; -interface IProps { +export interface IProps { seconds: number; } diff --git a/src/components/views/voice_messages/LiveRecordingClock.tsx b/src/components/views/voice_messages/LiveRecordingClock.tsx new file mode 100644 index 0000000000..f88bb63ac7 --- /dev/null +++ b/src/components/views/voice_messages/LiveRecordingClock.tsx @@ -0,0 +1,26 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import Clock, { IProps as IClockProps } from "./Clock"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +/** + * A clock for a live recording. + */ +@replaceableComponent("views.voice_messages.LiveRecordingClock") +export default class LiveRecordingClock extends React.PureComponent { + public render() { + return ; + } +} diff --git a/src/components/views/voice_messages/LiveRecordingWaveform.tsx b/src/components/views/voice_messages/LiveRecordingWaveform.tsx new file mode 100644 index 0000000000..3d3169d764 --- /dev/null +++ b/src/components/views/voice_messages/LiveRecordingWaveform.tsx @@ -0,0 +1,29 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import Waveform, { IProps as IWaveformProps } from "./Waveform"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +/** + * A waveform which shows the waveform of a live recording + */ +@replaceableComponent("views.voice_messages.LiveRecordingWaveform") +export default class LiveRecordingWaveform extends React.PureComponent { + public static defaultProps = { + progress: 1, + }; + public render() { + return ; + } +} diff --git a/src/components/views/voice_messages/Waveform.tsx b/src/components/views/voice_messages/Waveform.tsx index ae880933e6..5a4447065a 100644 --- a/src/components/views/voice_messages/Waveform.tsx +++ b/src/components/views/voice_messages/Waveform.tsx @@ -15,10 +15,10 @@ limitations under the License. */ import React from "react"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; import classNames from "classnames"; -interface IProps { +export interface IProps { relHeights: number[]; // relative heights (0-1) progress: number; // percent complete, 0-1, default 100% } From 76308de532680138de8c9fc1679c0090e8a4c843 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 23 Jun 2021 19:34:25 -0600 Subject: [PATCH 4/8] Add optional mark function callback --- src/utils/MarkedExecution.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/MarkedExecution.ts b/src/utils/MarkedExecution.ts index b0b8fdf63d..01cc91adce 100644 --- a/src/utils/MarkedExecution.ts +++ b/src/utils/MarkedExecution.ts @@ -26,9 +26,11 @@ export class MarkedExecution { /** * Creates a MarkedExecution for the provided function. - * @param fn The function to be called upon trigger if marked. + * @param {Function} fn The function to be called upon trigger if marked. + * @param {Function} onMarkCallback A function that is called when a new mark is made. Not + * called if a mark is already flagged. */ - constructor(private fn: () => void) { + constructor(private fn: () => void, private onMarkCallback?: () => void) { } /** @@ -42,6 +44,7 @@ export class MarkedExecution { * Marks the function to be called upon trigger(). */ public mark() { + if (!this.marked) this.onMarkCallback?.(); this.marked = true; } From 21caa6df12366848ad06ca5b2bdb1b39d6471b50 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 24 Jun 2021 09:58:11 +0100 Subject: [PATCH 5/8] move live recording logic down the component tree --- .../views/rooms/VoiceRecordComposerTile.tsx | 4 +- .../voice_messages/LiveRecordingClock.tsx | 53 ++++++++++++++++-- .../voice_messages/LiveRecordingWaveform.tsx | 54 +++++++++++++++++-- src/utils/MarkedExecution.ts | 17 ++++-- 4 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 78babc37eb..4f75b7b87e 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -227,8 +227,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent - - + + ; } diff --git a/src/components/views/voice_messages/LiveRecordingClock.tsx b/src/components/views/voice_messages/LiveRecordingClock.tsx index f88bb63ac7..6786ae36f8 100644 --- a/src/components/views/voice_messages/LiveRecordingClock.tsx +++ b/src/components/views/voice_messages/LiveRecordingClock.tsx @@ -12,15 +12,62 @@ limitations under the License. */ import React from "react"; -import Clock, { IProps as IClockProps } from "./Clock"; +import Clock from "./Clock"; 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. */ @replaceableComponent("views.voice_messages.LiveRecordingClock") -export default class LiveRecordingClock extends React.PureComponent { +export default class LiveRecordingClock extends React.PureComponent { + 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() { - return ; + return ; } } diff --git a/src/components/views/voice_messages/LiveRecordingWaveform.tsx b/src/components/views/voice_messages/LiveRecordingWaveform.tsx index 3d3169d764..83449fddc8 100644 --- a/src/components/views/voice_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/voice_messages/LiveRecordingWaveform.tsx @@ -12,18 +12,66 @@ limitations under the License. */ import React from "react"; -import Waveform, { IProps as IWaveformProps } from "./Waveform"; +import Waveform from "./Waveform"; 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 */ @replaceableComponent("views.voice_messages.LiveRecordingWaveform") -export default class LiveRecordingWaveform extends React.PureComponent { +export default class LiveRecordingWaveform extends React.PureComponent { public static defaultProps = { 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() { - return ; + return ; } } diff --git a/src/utils/MarkedExecution.ts b/src/utils/MarkedExecution.ts index 01cc91adce..0123d94f71 100644 --- a/src/utils/MarkedExecution.ts +++ b/src/utils/MarkedExecution.ts @@ -22,7 +22,7 @@ limitations under the License. * The function starts unmarked. */ export class MarkedExecution { - private marked = false; + private _marked = false; /** * Creates a MarkedExecution for the provided function. @@ -33,26 +33,33 @@ export class MarkedExecution { 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. */ public reset() { - this.marked = false; + this._marked = false; } /** * Marks the function to be called upon trigger(). */ public mark() { - if (!this.marked) this.onMarkCallback?.(); - this.marked = true; + if (!this._marked) this.onMarkCallback?.(); + this._marked = true; } /** * If marked, the function will be called, otherwise this does nothing. */ public trigger() { - if (!this.marked) return; + if (!this._marked) return; this.reset(); // reset first just in case the fn() causes a trigger() this.fn(); } From 09c22c37ffc51d00fa2c6fa6c41fb65324b94030 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 24 Jun 2021 10:01:44 +0100 Subject: [PATCH 6/8] Add comment to specify variable origin --- res/css/views/voice_messages/_PlaybackContainer.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/voice_messages/_PlaybackContainer.scss b/res/css/views/voice_messages/_PlaybackContainer.scss index 944378fb19..f0e29900ab 100644 --- a/res/css/views/voice_messages/_PlaybackContainer.scss +++ b/res/css/views/voice_messages/_PlaybackContainer.scss @@ -39,6 +39,7 @@ limitations under the License. .mx_Waveform_bar { background-color: $voice-record-waveform-incomplete-fg-color; height: 100%; + /* Variable set by a JS component */ transform: scaleY(max(0.05, var(--barHeight))); &.mx_Waveform_bar_100pct { From 652e06a7b1e9be44c67afd8c3afda53d0a9fbb3e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 28 Jun 2021 09:24:35 +0100 Subject: [PATCH 7/8] remove cancelAnimationFrame complexity and rely on MarkedExecution class --- .../voice_messages/LiveRecordingClock.tsx | 26 +++++++------------ .../voice_messages/LiveRecordingWaveform.tsx | 26 +++++++------------ src/utils/MarkedExecution.ts | 7 ----- 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/src/components/views/voice_messages/LiveRecordingClock.tsx b/src/components/views/voice_messages/LiveRecordingClock.tsx index 6786ae36f8..49d5dd66e1 100644 --- a/src/components/views/voice_messages/LiveRecordingClock.tsx +++ b/src/components/views/voice_messages/LiveRecordingClock.tsx @@ -34,10 +34,16 @@ interface IState { @replaceableComponent("views.voice_messages.LiveRecordingClock") export default class LiveRecordingClock extends React.PureComponent { private seconds = 0; - private rafId: number; + private scheduledUpdate = new MarkedExecution( + () => this.updateClock(), + () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), + ) - state = { - seconds: 0, + constructor(props) { + super(props); + this.state = { + seconds: 0, + } } componentDidMount() { @@ -47,24 +53,10 @@ export default class LiveRecordingClock extends React.PureComponent 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() { diff --git a/src/components/views/voice_messages/LiveRecordingWaveform.tsx b/src/components/views/voice_messages/LiveRecordingWaveform.tsx index 83449fddc8..640f7eb129 100644 --- a/src/components/views/voice_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/voice_messages/LiveRecordingWaveform.tsx @@ -38,10 +38,16 @@ export default class LiveRecordingWaveform extends React.PureComponent this.updateWaveform(), + () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), + ) - state = { - waveform: [], + constructor(props) { + super(props); + this.state = { + waveform: [], + } } componentDidMount() { @@ -51,24 +57,10 @@ export default class LiveRecordingWaveform extends React.PureComponent 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() { diff --git a/src/utils/MarkedExecution.ts b/src/utils/MarkedExecution.ts index 0123d94f71..624ab499de 100644 --- a/src/utils/MarkedExecution.ts +++ b/src/utils/MarkedExecution.ts @@ -33,13 +33,6 @@ export class MarkedExecution { 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. */ From e519e704e9e89be37b90a7f75c4a6e7a61221b9a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jun 2021 20:40:11 -0600 Subject: [PATCH 8/8] Apply suggestions from code review --- .../views/voice_messages/LiveRecordingClock.tsx | 6 +++--- .../views/voice_messages/LiveRecordingWaveform.tsx | 6 +++--- src/utils/MarkedExecution.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/views/voice_messages/LiveRecordingClock.tsx b/src/components/views/voice_messages/LiveRecordingClock.tsx index 49d5dd66e1..2a20e9bfec 100644 --- a/src/components/views/voice_messages/LiveRecordingClock.tsx +++ b/src/components/views/voice_messages/LiveRecordingClock.tsx @@ -37,13 +37,13 @@ export default class LiveRecordingClock extends React.PureComponent this.updateClock(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), - ) + ); constructor(props) { super(props); this.state = { seconds: 0, - } + }; } componentDidMount() { @@ -56,7 +56,7 @@ export default class LiveRecordingClock extends React.PureComponent this.updateWaveform(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), - ) + ); constructor(props) { super(props); this.state = { waveform: [], - } + }; } componentDidMount() { diff --git a/src/utils/MarkedExecution.ts b/src/utils/MarkedExecution.ts index 624ab499de..01cc91adce 100644 --- a/src/utils/MarkedExecution.ts +++ b/src/utils/MarkedExecution.ts @@ -22,7 +22,7 @@ limitations under the License. * The function starts unmarked. */ export class MarkedExecution { - private _marked = false; + private marked = false; /** * Creates a MarkedExecution for the provided function. @@ -37,22 +37,22 @@ export class MarkedExecution { * Resets the mark without calling the function. */ public reset() { - this._marked = false; + this.marked = false; } /** * Marks the function to be called upon trigger(). */ public mark() { - if (!this._marked) this.onMarkCallback?.(); - this._marked = true; + if (!this.marked) this.onMarkCallback?.(); + this.marked = true; } /** * If marked, the function will be called, otherwise this does nothing. */ public trigger() { - if (!this._marked) return; + if (!this.marked) return; this.reset(); // reset first just in case the fn() causes a trigger() this.fn(); }