mirror of https://github.com/vector-im/riot-web
Merge pull request #5352 from matrix-org/hs/mvideobody
Do not preload encrypted videos|images unless autoplay or thumbnailing is onpull/21833/head
commit
0adb920448
|
@ -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});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue