Convert images, audio, and voice messages over to the new helper

This commit is contained in:
Travis Ralston 2021-07-15 16:37:48 -06:00
parent 040802e29f
commit 5fce0ccd9d
3 changed files with 41 additions and 117 deletions

View File

@ -18,22 +18,22 @@ import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Playback } from "../../../voice/Playback"; import { Playback } from "../../../voice/Playback";
import MFileBody from "./MFileBody";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { mediaFromContent } from "../../../customisations/Media";
import { decryptFile } from "../../../utils/DecryptFile";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
import AudioPlayer from "../audio_messages/AudioPlayer"; import AudioPlayer from "../audio_messages/AudioPlayer";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
import { TileShape } from "../rooms/EventTile";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
tileShape?: TileShape;
mediaEventHelper: MediaEventHelper;
} }
interface IState { interface IState {
error?: Error; error?: Error;
playback?: Playback; playback?: Playback;
decryptedBlob?: Blob;
} }
@replaceableComponent("views.messages.MAudioBody") @replaceableComponent("views.messages.MAudioBody")
@ -46,33 +46,34 @@ export default class MAudioBody extends React.PureComponent<IProps, IState> {
public async componentDidMount() { public async componentDidMount() {
let buffer: ArrayBuffer; let buffer: ArrayBuffer;
const content: IMediaEventContent = this.props.mxEvent.getContent();
const media = mediaFromContent(content);
if (media.isEncrypted) {
try { try {
const blob = await decryptFile(content.file); try {
const blob = await this.props.mediaEventHelper.sourceBlob.value;
buffer = await blob.arrayBuffer(); buffer = await blob.arrayBuffer();
this.setState({ decryptedBlob: blob });
} catch (e) { } catch (e) {
this.setState({ error: e }); this.setState({ error: e });
console.warn("Unable to decrypt audio message", e); console.warn("Unable to decrypt audio message", e);
return; // stop processing the audio file return; // stop processing the audio file
} }
} else {
try {
buffer = await media.downloadSource().then(r => r.blob()).then(r => r.arrayBuffer());
} catch (e) { } catch (e) {
this.setState({ error: e }); this.setState({ error: e });
console.warn("Unable to download audio message", e); console.warn("Unable to decrypt/download audio message", e);
return; // stop processing the audio file return; // stop processing the audio file
} }
}
// We should have a buffer to work with now: let's set it up // We should have a buffer to work with now: let's set it up
const playback = new Playback(buffer);
// Note: we don't actually need a waveform to render an audio event, but voice messages do.
const content = this.props.mxEvent.getContent<IMediaEventContent>();
const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map(p => p / 1024);
// We should have a buffer to work with now: let's set it up
const playback = new Playback(buffer, waveform);
playback.clockInfo.populatePlaceholdersFrom(this.props.mxEvent); playback.clockInfo.populatePlaceholdersFrom(this.props.mxEvent);
this.setState({ playback }); this.setState({ playback });
// Note: the RecordingPlayback component will handle preparing the Playback class for us.
// Note: the components later on will handle preparing the Playback class for us.
} }
public componentWillUnmount() { public componentWillUnmount() {
@ -103,7 +104,7 @@ export default class MAudioBody extends React.PureComponent<IProps, IState> {
return ( return (
<span className="mx_MAudioBody"> <span className="mx_MAudioBody">
<AudioPlayer playback={this.state.playback} mediaName={this.props.mxEvent.getContent().body} /> <AudioPlayer playback={this.state.playback} mediaName={this.props.mxEvent.getContent().body} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} /> {/*<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />*/}
</span> </span>
); );
} }

View File

@ -1,6 +1,5 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2018, 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2018, 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -21,7 +20,6 @@ import { Blurhash } from "react-blurhash";
import MFileBody from './MFileBody'; import MFileBody from './MFileBody';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
@ -34,6 +32,7 @@ import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent';
import ImageView from '../elements/ImageView'; import ImageView from '../elements/ImageView';
import { SyncState } from 'matrix-js-sdk/src/sync.api'; import { SyncState } from 'matrix-js-sdk/src/sync.api';
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
export interface IProps { export interface IProps {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
@ -46,6 +45,7 @@ export interface IProps {
/* the permalinkCreator */ /* the permalinkCreator */
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
mediaEventHelper: MediaEventHelper;
} }
interface IState { interface IState {
@ -257,38 +257,24 @@ export default class MImageBody extends React.Component<IProps, IState> {
} }
} }
private downloadImage(): void { private async downloadImage() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) { if (this.props.mediaEventHelper.media.isEncrypted && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null); try {
if (content.info && content.info.thumbnail_file) { const thumbnailUrl = await this.props.mediaEventHelper.thumbnailUrl.value;
thumbnailPromise = decryptFile(
content.info.thumbnail_file,
).then(function(blob) {
return URL.createObjectURL(blob);
});
}
let decryptedBlob;
thumbnailPromise.then((thumbnailUrl) => {
return decryptFile(content.file).then(function(blob) {
decryptedBlob = blob;
return URL.createObjectURL(blob);
}).then((contentUrl) => {
if (this.unmounted) return;
this.setState({ this.setState({
decryptedUrl: contentUrl, decryptedUrl: await this.props.mediaEventHelper.sourceUrl.value,
decryptedThumbnailUrl: thumbnailUrl, decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: decryptedBlob, decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value,
}); });
}); } catch (err) {
}).catch((err) => {
if (this.unmounted) return; if (this.unmounted) return;
console.warn("Unable to decrypt attachment: ", err); console.warn("Unable to decrypt attachment: ", err);
// Set a placeholder image when we can't decrypt the image. // Set a placeholder image when we can't decrypt the image.
this.setState({ this.setState({
error: err, error: err,
}); });
}); }
} }
} }
@ -300,10 +286,10 @@ export default class MImageBody extends React.Component<IProps, IState> {
localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true"; localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true";
if (showImage) { if (showImage) {
// Don't download anything becaue we don't want to display anything. // noinspection JSIgnoredPromiseFromCall
this.downloadImage(); this.downloadImage();
this.setState({ showImage: true }); this.setState({ showImage: true });
} } // else don't download anything because we don't want to display anything.
this._afterComponentDidMount(); this._afterComponentDidMount();
} }
@ -316,13 +302,6 @@ export default class MImageBody extends React.Component<IProps, IState> {
componentWillUnmount() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
this.context.removeListener('sync', this.onClientSync); this.context.removeListener('sync', this.onClientSync);
if (this.state.decryptedUrl) {
URL.revokeObjectURL(this.state.decryptedUrl);
}
if (this.state.decryptedThumbnailUrl) {
URL.revokeObjectURL(this.state.decryptedThumbnailUrl);
}
} }
protected messageContent( protected messageContent(

View File

@ -15,72 +15,16 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Playback } from "../../../voice/Playback";
import MFileBody from "./MFileBody";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { mediaFromContent } from "../../../customisations/Media";
import { decryptFile } from "../../../utils/DecryptFile";
import RecordingPlayback from "../audio_messages/RecordingPlayback"; import RecordingPlayback from "../audio_messages/RecordingPlayback";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import MAudioBody from "./MAudioBody";
import { TileShape } from "../rooms/EventTile";
interface IProps {
mxEvent: MatrixEvent;
tileShape?: TileShape;
}
interface IState {
error?: Error;
playback?: Playback;
decryptedBlob?: Blob;
}
@replaceableComponent("views.messages.MVoiceMessageBody") @replaceableComponent("views.messages.MVoiceMessageBody")
export default class MVoiceMessageBody extends React.PureComponent<IProps, IState> { export default class MVoiceMessageBody extends MAudioBody {
constructor(props: IProps) {
super(props);
this.state = {}; // A voice message is an audio file but rendered in a special way.
}
public async componentDidMount() {
let buffer: ArrayBuffer;
const content: IMediaEventContent = this.props.mxEvent.getContent();
const media = mediaFromContent(content);
if (media.isEncrypted) {
try {
const blob = await decryptFile(content.file);
buffer = await blob.arrayBuffer();
this.setState({ decryptedBlob: blob });
} catch (e) {
this.setState({ error: e });
console.warn("Unable to decrypt voice message", e);
return; // stop processing the audio file
}
} else {
try {
buffer = await media.downloadSource().then(r => r.blob()).then(r => r.arrayBuffer());
} catch (e) {
this.setState({ error: e });
console.warn("Unable to download voice message", e);
return; // stop processing the audio file
}
}
const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map(p => p / 1024);
// We should have a buffer to work with now: let's set it up
const playback = new Playback(buffer, waveform);
this.setState({ playback });
// Note: the RecordingPlayback component will handle preparing the Playback class for us.
}
public componentWillUnmount() {
this.state.playback?.destroy();
}
public render() { public render() {
if (this.state.error) { if (this.state.error) {
@ -106,7 +50,7 @@ export default class MVoiceMessageBody extends React.PureComponent<IProps, IStat
return ( return (
<span className="mx_MVoiceMessageBody"> <span className="mx_MVoiceMessageBody">
<RecordingPlayback playback={this.state.playback} tileShape={this.props.tileShape} /> <RecordingPlayback playback={this.state.playback} tileShape={this.props.tileShape} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} /> {/*<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />*/}
</span> </span>
); );
} }