Merge pull request #5352 from matrix-org/hs/mvideobody

Do not preload encrypted videos|images unless autoplay or thumbnailing is on
pull/21833/head
Travis Ralston 2020-10-27 18:56:29 -06:00 committed by GitHub
commit 0adb920448
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 37 deletions

View File

@ -85,6 +85,7 @@ export default class MImageBody extends React.Component {
showImage() { showImage() {
localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true"); localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true");
this.setState({showImage: true}); this.setState({showImage: true});
this._downloadImage();
} }
onClick(ev) { onClick(ev) {
@ -253,10 +254,7 @@ export default class MImageBody extends React.Component {
} }
} }
componentDidMount() { _downloadImage() {
this.unmounted = false;
this.context.on('sync', this.onClientSync);
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) { if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null); let thumbnailPromise = Promise.resolve(null);
@ -289,9 +287,18 @@ export default class MImageBody extends React.Component {
}); });
}); });
} }
}
// Remember that the user wanted to show this particular image componentDidMount() {
if (!this.state.showImage && localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true") { this.unmounted = false;
this.context.on('sync', this.onClientSync);
const showImage = 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}); this.setState({showImage: true});
} }

View File

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import MFileBody from './MFileBody'; import MFileBody from './MFileBody';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile'; import { decryptFile } from '../../../utils/DecryptFile';
@ -24,23 +23,34 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
export default class MVideoBody extends React.Component { interface IProps {
static propTypes = {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: any;
/* called when the video has loaded */ /* called when the video has loaded */
onHeightChanged: PropTypes.func.isRequired, onHeightChanged: () => void;
}; }
state = { interface IState {
decryptedUrl: string|null,
decryptedThumbnailUrl: string|null,
decryptedBlob: Blob|null,
error: any|null,
fetchingData: boolean,
}
export default class MVideoBody extends React.PureComponent<IProps, IState> {
constructor(props) {
super(props);
this.state = {
fetchingData: false,
decryptedUrl: null, decryptedUrl: null,
decryptedThumbnailUrl: null, decryptedThumbnailUrl: null,
decryptedBlob: null, decryptedBlob: null,
error: null, error: null,
}; }
}
thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) { thumbScale(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) {
if (!fullWidth || !fullHeight) { if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy // log this because it's spammy
@ -61,7 +71,7 @@ export default class MVideoBody extends React.Component {
} }
} }
_getContentUrl() { _getContentUrl(): string|null {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
@ -70,7 +80,7 @@ export default class MVideoBody extends React.Component {
} }
} }
_getThumbUrl() { _getThumbUrl(): string|null {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
return this.state.decryptedThumbnailUrl; return this.state.decryptedThumbnailUrl;
@ -81,7 +91,8 @@ export default class MVideoBody extends React.Component {
} }
} }
componentDidMount() { async componentDidMount() {
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean;
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) { if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null); let thumbnailPromise = Promise.resolve(null);
@ -92,26 +103,33 @@ export default class MVideoBody extends React.Component {
return URL.createObjectURL(blob); return URL.createObjectURL(blob);
}); });
} }
let decryptedBlob; try {
thumbnailPromise.then((thumbnailUrl) => { const thumbnailUrl = await thumbnailPromise;
return decryptFile(content.file).then(function(blob) { if (autoplay) {
decryptedBlob = blob; console.log("Preloading video");
return URL.createObjectURL(blob); const decryptedBlob = await decryptFile(content.file);
}).then((contentUrl) => { const contentUrl = URL.createObjectURL(decryptedBlob);
this.setState({ this.setState({
decryptedUrl: contentUrl, decryptedUrl: contentUrl,
decryptedThumbnailUrl: thumbnailUrl, decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: decryptedBlob, decryptedBlob: decryptedBlob,
}); });
this.props.onHeightChanged(); this.props.onHeightChanged();
} else {
console.log("NOT preloading video");
this.setState({
decryptedUrl: null,
decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: null,
}); });
}).catch((err) => { }
} catch (err) {
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,
}); });
}); }
} }
} }
@ -124,8 +142,35 @@ export default class MVideoBody extends React.Component {
} }
} }
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() { render() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos");
if (this.state.error !== null) { if (this.state.error !== null) {
return ( return (
@ -136,7 +181,8 @@ export default class MVideoBody extends React.Component {
); );
} }
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 // Need to decrypt the attachment
// The attachment is decrypted in componentDidMount. // The attachment is decrypted in componentDidMount.
// For now add an img tag with a spinner. // For now add an img tag with a spinner.
@ -151,7 +197,6 @@ export default class MVideoBody extends React.Component {
const contentUrl = this._getContentUrl(); const contentUrl = this._getContentUrl();
const thumbUrl = this._getThumbUrl(); const thumbUrl = this._getThumbUrl();
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos");
let height = null; let height = null;
let width = null; let width = null;
let poster = null; let poster = null;
@ -170,9 +215,9 @@ export default class MVideoBody extends React.Component {
} }
return ( return (
<span className="mx_MVideoBody"> <span className="mx_MVideoBody">
<video className="mx_MVideoBody" src={contentUrl} alt={content.body} <video className="mx_MVideoBody" src={contentUrl} title={content.body}
controls preload={preload} muted={autoplay} autoPlay={autoplay} controls preload={preload} muted={autoplay} autoPlay={autoplay}
height={height} width={width} poster={poster}> height={height} width={width} poster={poster} onPlay={this._videoOnPlay.bind(this)}>
</video> </video>
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} /> <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span> </span>