Prevent voice message from displaying spurious errors (#30736)
* fix: avoid to render `AudioPlayerViewModel` when `MAudioBody` is inherited * fix: avoid `Playback.prepare` to fail when called twice * fix: add `decoding` to playback type * refactor: fix circular deps * refactor: extract `MockedPlayback` from `AudioPlayerViewModel` * test: add `MAudioBody` basic test * test: add tests for `MVoiceMessageBody` * fix: lint
This commit is contained in:
@@ -74,7 +74,11 @@ export default class RecordingPlayback extends AudioPlayerBase<IProps> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_MediaBody mx_VoiceMessagePrimaryContainer" onKeyDown={this.onKeyDown}>
|
||||
<div
|
||||
className="mx_MediaBody mx_VoiceMessagePrimaryContainer"
|
||||
onKeyDown={this.onKeyDown}
|
||||
data-testid="recording-playback"
|
||||
>
|
||||
<PlayPauseButton
|
||||
playback={this.props.playback}
|
||||
playbackPhase={this.state.playbackPhase}
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { type JSX, useEffect, useMemo } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type IContent } from "matrix-js-sdk/src/matrix";
|
||||
import { type MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
@@ -17,8 +17,6 @@ import { _t } from "../../../languageHandler";
|
||||
import MFileBody from "./MFileBody";
|
||||
import { type IBodyProps } from "./IBodyProps";
|
||||
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
||||
import { isVoiceMessage } from "../../../utils/EventUtils";
|
||||
import { PlaybackQueue } from "../../../audio/PlaybackQueue";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
import { AudioPlayerViewModel } from "../../../viewmodels/audio/AudioPlayerViewModel";
|
||||
@@ -27,7 +25,6 @@ import { AudioPlayerView } from "../../../shared-components/audio/AudioPlayerVie
|
||||
interface IState {
|
||||
error?: boolean;
|
||||
playback?: Playback;
|
||||
audioPlayerVm?: AudioPlayerViewModel;
|
||||
}
|
||||
|
||||
export default class MAudioBody extends React.PureComponent<IBodyProps, IState> {
|
||||
@@ -38,7 +35,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||
|
||||
public async componentDidMount(): Promise<void> {
|
||||
let buffer: ArrayBuffer;
|
||||
|
||||
try {
|
||||
try {
|
||||
const blob = await this.props.mediaEventHelper!.sourceBlob.value;
|
||||
@@ -63,18 +59,16 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||
// We should have a buffer to work with now: let's set it up
|
||||
const playback = PlaybackManager.instance.createPlaybackInstance(buffer, waveform);
|
||||
playback.clockInfo.populatePlaceholdersFrom(this.props.mxEvent);
|
||||
this.setState({ playback, audioPlayerVm: new AudioPlayerViewModel({ playback, mediaName: content.body }) });
|
||||
this.setState({ playback });
|
||||
|
||||
if (isVoiceMessage(this.props.mxEvent)) {
|
||||
PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()!).unsortedEnqueue(this.props.mxEvent, playback);
|
||||
}
|
||||
|
||||
// Note: the components later on will handle preparing the Playback class for us.
|
||||
this.onMount(playback);
|
||||
// Note: the components later on will handle preparing the Playback class for us
|
||||
}
|
||||
|
||||
protected onMount(playback: Playback): void {}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.state.playback?.destroy();
|
||||
this.state.audioPlayerVm?.dispose();
|
||||
}
|
||||
|
||||
protected get showFileBody(): boolean {
|
||||
@@ -116,9 +110,35 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||
// At this point we should have a playable state
|
||||
return (
|
||||
<span className="mx_MAudioBody">
|
||||
{this.state.audioPlayerVm && <AudioPlayerView vm={this.state.audioPlayerVm} />}
|
||||
<AudioPlayer playback={this.state.playback} mediaName={this.props.mxEvent.getContent().body} />
|
||||
{this.showFileBody && <MFileBody {...this.props} showGenericPlaceholder={false} />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface AudioPlayerProps {
|
||||
/**
|
||||
* The playback instance to control audio playback.
|
||||
*/
|
||||
playback: Playback;
|
||||
/**
|
||||
* The name of the media being played
|
||||
*/
|
||||
mediaName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioPlayer component that initializes the AudioPlayerViewModel and renders the AudioPlayerView.
|
||||
*/
|
||||
function AudioPlayer({ playback, mediaName }: AudioPlayerProps): JSX.Element {
|
||||
const vm = useMemo(() => new AudioPlayerViewModel({ playback, mediaName }), [playback, mediaName]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
vm.dispose();
|
||||
};
|
||||
}, [vm]);
|
||||
|
||||
return <AudioPlayerView vm={vm} />;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,17 @@ import RecordingPlayback from "../audio_messages/RecordingPlayback";
|
||||
import MAudioBody from "./MAudioBody";
|
||||
import MFileBody from "./MFileBody";
|
||||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
import { isVoiceMessage } from "../../../utils/EventUtils";
|
||||
import { PlaybackQueue } from "../../../audio/PlaybackQueue";
|
||||
import { type Playback } from "../../../audio/Playback";
|
||||
|
||||
export default class MVoiceMessageBody extends MAudioBody {
|
||||
protected onMount(playback: Playback): void {
|
||||
if (isVoiceMessage(this.props.mxEvent)) {
|
||||
PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()!).unsortedEnqueue(this.props.mxEvent, playback);
|
||||
}
|
||||
}
|
||||
|
||||
// A voice message is an audio file but rendered in a special way.
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.error) {
|
||||
|
||||
Reference in New Issue
Block a user