From bbb4c7f8014937cce9bb67b6f0d43c0491a24204 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 23 Oct 2020 16:31:30 +0100 Subject: [PATCH 1/4] Convert MVideoBody to typescript --- .../{MVideoBody.js => MVideoBody.tsx} | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) rename src/components/views/messages/{MVideoBody.js => MVideoBody.tsx} (88%) diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.tsx similarity index 88% rename from src/components/views/messages/MVideoBody.js rename to src/components/views/messages/MVideoBody.tsx index 86bf41699b1..6712717007c 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.tsx @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import MFileBody from './MFileBody'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; @@ -24,23 +23,32 @@ import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; import InlineSpinner from '../elements/InlineSpinner'; -export default class MVideoBody extends React.Component { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, +interface IProps { + /* the MatrixEvent to show */ + mxEvent: any; + /* called when the video has loaded */ + onHeightChanged: () => void; +} - /* called when the video has loaded */ - onHeightChanged: PropTypes.func.isRequired, - }; +interface IState { + decryptedUrl: string|null, + decryptedThumbnailUrl: string|null, + decryptedBlob: Blob|null, + error: string|null, +} - state = { - decryptedUrl: null, - decryptedThumbnailUrl: null, - decryptedBlob: null, - error: null, - }; +export default class MVideoBody extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + decryptedUrl: null, + decryptedThumbnailUrl: null, + decryptedBlob: null, + error: null, + } + } - thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) { + thumbScale(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) { if (!fullWidth || !fullHeight) { // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // log this because it's spammy @@ -61,7 +69,7 @@ export default class MVideoBody extends React.Component { } } - _getContentUrl() { + _getContentUrl(): string { const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { return this.state.decryptedUrl; @@ -70,7 +78,7 @@ export default class MVideoBody extends React.Component { } } - _getThumbUrl() { + _getThumbUrl(): string|null { const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { return this.state.decryptedThumbnailUrl; From 7164284bb6f9a8fce70a9cce5f7d2bbe7ef3e195 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 23 Oct 2020 16:33:40 +0100 Subject: [PATCH 2/4] Do not download encrypted video if autoplay is not on --- src/components/views/messages/MVideoBody.tsx | 67 +++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 6712717007c..734099dbd9a 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -34,13 +34,15 @@ interface IState { decryptedUrl: string|null, decryptedThumbnailUrl: string|null, decryptedBlob: Blob|null, - error: string|null, + error: any|null, + fetchingData: boolean, } export default class MVideoBody extends React.PureComponent { constructor(props) { super(props); this.state = { + fetchingData: false, decryptedUrl: null, decryptedThumbnailUrl: null, decryptedBlob: null, @@ -69,7 +71,7 @@ export default class MVideoBody extends React.PureComponent { } } - _getContentUrl(): string { + _getContentUrl(): string|null { const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { return this.state.decryptedUrl; @@ -89,7 +91,8 @@ export default class MVideoBody extends React.PureComponent { } } - componentDidMount() { + async componentDidMount() { + const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean; const content = this.props.mxEvent.getContent(); if (content.file !== undefined && this.state.decryptedUrl === null) { let thumbnailPromise = Promise.resolve(null); @@ -100,26 +103,33 @@ export default class MVideoBody extends React.PureComponent { return URL.createObjectURL(blob); }); } - let decryptedBlob; - thumbnailPromise.then((thumbnailUrl) => { - return decryptFile(content.file).then(function(blob) { - decryptedBlob = blob; - return URL.createObjectURL(blob); - }).then((contentUrl) => { + try { + const thumbnailUrl = await thumbnailPromise; + if (autoplay) { + console.log("Preloading video"); + const decryptedBlob = await decryptFile(content.file); + const contentUrl = URL.createObjectURL(decryptedBlob); this.setState({ decryptedUrl: contentUrl, decryptedThumbnailUrl: thumbnailUrl, decryptedBlob: decryptedBlob, }); this.props.onHeightChanged(); - }); - }).catch((err) => { + } else { + console.log("NOT preloading video"); + this.setState({ + decryptedUrl: null, + decryptedThumbnailUrl: thumbnailUrl, + decryptedBlob: null, + }); + } + } catch (err) { console.warn("Unable to decrypt attachment: ", err); // Set a placeholder image when we can't decrypt the image. this.setState({ error: err, }); - }); + } } } @@ -132,8 +142,35 @@ export default class MVideoBody extends React.PureComponent { } } + async _videoOnPlay() { + const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean; + if (autoplay || this.state.decryptedUrl || this.state.fetchingData || this.state.error) { + // The video has or will have the data. + return; + } + this.setState({ + // To stop subsequent download attempts + fetchingData: true, + }); + const content = this.props.mxEvent.getContent(); + if (!content.file) { + this.setState({ + error: "No file given in content", + }); + return; + } + const decryptedBlob = await decryptFile(content.file); + const contentUrl = URL.createObjectURL(decryptedBlob); + this.setState({ + decryptedUrl: contentUrl, + decryptedBlob: decryptedBlob, + }); + this.props.onHeightChanged(); + } + render() { const content = this.props.mxEvent.getContent(); + const autoplay = SettingsStore.getValue("autoplayGifsAndVideos"); if (this.state.error !== null) { return ( @@ -144,7 +181,8 @@ export default class MVideoBody extends React.PureComponent { ); } - if (content.file !== undefined && this.state.decryptedUrl === null) { + // Important: If we aren't autoplaying and we haven't decrypred it yet, show a video with a poster. + if (content.file !== undefined && this.state.decryptedUrl === null && autoplay) { // Need to decrypt the attachment // The attachment is decrypted in componentDidMount. // For now add an img tag with a spinner. @@ -159,7 +197,6 @@ export default class MVideoBody extends React.PureComponent { const contentUrl = this._getContentUrl(); const thumbUrl = this._getThumbUrl(); - const autoplay = SettingsStore.getValue("autoplayGifsAndVideos"); let height = null; let width = null; let poster = null; @@ -180,7 +217,7 @@ export default class MVideoBody extends React.PureComponent { From e5228e279559a3147a76040ba7bf19ee7e3b9771 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 23 Oct 2020 16:42:23 +0100 Subject: [PATCH 3/4] alt is not valid on video element --- src/components/views/messages/MVideoBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 734099dbd9a..3378670f54d 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -215,7 +215,7 @@ export default class MVideoBody extends React.PureComponent { } return ( - From 7eb54cc83c995001e288f69eabc099bd038fec94 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 23 Oct 2020 17:16:09 +0100 Subject: [PATCH 4/4] Also only download images after clicking on them --- src/components/views/messages/MImageBody.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index c92ae475bf1..a8cdc17abf4 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -85,6 +85,7 @@ export default class MImageBody extends React.Component { showImage() { localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true"); this.setState({showImage: true}); + this._downloadImage(); } onClick(ev) { @@ -253,10 +254,7 @@ export default class MImageBody extends React.Component { } } - componentDidMount() { - this.unmounted = false; - this.context.on('sync', this.onClientSync); - + _downloadImage() { const content = this.props.mxEvent.getContent(); if (content.file !== undefined && this.state.decryptedUrl === null) { let thumbnailPromise = Promise.resolve(null); @@ -289,9 +287,18 @@ export default class MImageBody extends React.Component { }); }); } + } + + componentDidMount() { + this.unmounted = false; + this.context.on('sync', this.onClientSync); + + const showImage = this.state.showImage || + localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true"; - // Remember that the user wanted to show this particular image - if (!this.state.showImage && localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true") { + if (showImage) { + // Don't download anything becaue we don't want to display anything. + this._downloadImage(); this.setState({showImage: true}); }