Support blurhash for video posters

This commit is contained in:
Michael Telatynski 2021-05-21 21:20:00 +01:00
parent dbca9b4625
commit 3a2e5389f6

View file

@ -16,6 +16,8 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { decode } from "blurhash";
import MFileBody from './MFileBody'; import MFileBody from './MFileBody';
import { decryptFile } from '../../../utils/DecryptFile'; import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -23,6 +25,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromContent} from "../../../customisations/Media"; import {mediaFromContent} from "../../../customisations/Media";
import {BLURHASH_FIELD} from "../../../ContentMessages";
interface IProps { interface IProps {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
@ -37,6 +40,8 @@ interface IState {
decryptedBlob: Blob|null, decryptedBlob: Blob|null,
error: any|null, error: any|null,
fetchingData: boolean, fetchingData: boolean,
posterLoading: boolean;
blurhashUrl: string;
} }
@replaceableComponent("views.messages.MVideoBody") @replaceableComponent("views.messages.MVideoBody")
@ -51,10 +56,12 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
decryptedThumbnailUrl: null, decryptedThumbnailUrl: null,
decryptedBlob: null, decryptedBlob: null,
error: null, error: null,
posterLoading: false,
blurhashUrl: null,
} }
} }
thumbScale(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) { thumbScale(fullWidth: number, fullHeight: number, thumbWidth = 480, thumbHeight = 360) {
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
@ -92,8 +99,11 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
private getThumbUrl(): string|null { private getThumbUrl(): string|null {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const media = mediaFromContent(content); const media = mediaFromContent(content);
if (media.isEncrypted) {
if (media.isEncrypted && this.state.decryptedThumbnailUrl) {
return this.state.decryptedThumbnailUrl; return this.state.decryptedThumbnailUrl;
} else if (this.state.posterLoading) {
return this.state.blurhashUrl;
} else if (media.hasThumbnail) { } else if (media.hasThumbnail) {
return media.thumbnailHttp; return media.thumbnailHttp;
} else { } else {
@ -101,18 +111,57 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
} }
} }
private loadBlurhash() {
const info = this.props.mxEvent.getContent()?.info;
if (!info[BLURHASH_FIELD]) return;
const canvas = document.createElement("canvas");
let width = info.w;
let height = info.h;
const scale = this.thumbScale(info.w, info.h);
if (scale) {
width = Math.floor(info.w * scale);
height = Math.floor(info.h * scale);
}
canvas.width = width;
canvas.height = height;
const pixels = decode(info[BLURHASH_FIELD], width, height);
const ctx = canvas.getContext("2d");
const imgData = ctx.createImageData(width, height);
imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0);
this.setState({
blurhashUrl: canvas.toDataURL(),
posterLoading: true,
});
const content = this.props.mxEvent.getContent();
const media = mediaFromContent(content);
if (media.hasThumbnail) {
const image = new Image();
image.onload = () => {
this.setState({ posterLoading: false });
};
image.src = media.thumbnailHttp;
}
}
async componentDidMount() { async componentDidMount() {
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean; const autoplay = SettingsStore.getValue("autoplayGifsAndVideos") as boolean;
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
this.loadBlurhash();
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);
if (content.info && content.info.thumbnail_file) { if (content?.info?.thumbnail_file) {
thumbnailPromise = decryptFile( thumbnailPromise = decryptFile(content.info.thumbnail_file)
content.info.thumbnail_file, .then(blob => URL.createObjectURL(blob));
).then(function(blob) {
return URL.createObjectURL(blob);
});
} }
try { try {
const thumbnailUrl = await thumbnailPromise; const thumbnailUrl = await thumbnailPromise;
if (autoplay) { if (autoplay) {
@ -218,7 +267,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
let poster = null; let poster = null;
let preload = "metadata"; let preload = "metadata";
if (content.info) { if (content.info) {
const scale = this.thumbScale(content.info.w, content.info.h, 480, 360); const scale = this.thumbScale(content.info.w, content.info.h);
if (scale) { if (scale) {
width = Math.floor(content.info.w * scale); width = Math.floor(content.info.w * scale);
height = Math.floor(content.info.h * scale); height = Math.floor(content.info.h * scale);