Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 953e314

Browse files
authored
Make video sizing consistent with images (#8102)
* Make video sizing consistent with images * Test suggestedSize * Constrain width of media in large mode
1 parent bff1ef3 commit 953e314

File tree

5 files changed

+90
-89
lines changed

5 files changed

+90
-89
lines changed

res/css/views/messages/_MVideoBody.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ limitations under the License.
1616

1717
span.mx_MVideoBody {
1818
video.mx_MVideoBody {
19-
max-width: 100%;
20-
height: auto;
2119
border-radius: $timeline-image-border-radius;
2220
}
2321
}

src/components/views/messages/MImageBody.tsx

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -377,28 +377,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
377377
infoHeight = this.state.loadedImageDimensions.naturalHeight;
378378
}
379379

380-
// The maximum size of the thumbnail as it is rendered as an <img>
381-
// check for any height constraints
382-
const imageSize = SettingsStore.getValue("Images.size") as ImageSize;
383-
const isPortrait = infoWidth < infoHeight;
384-
const suggestedAndPossibleWidth = Math.min(suggestedImageSize(imageSize, isPortrait).w, infoWidth);
385-
const suggestedAndPossibleHeight = Math.min(suggestedImageSize(imageSize, isPortrait).h, infoHeight);
386-
const aspectRatio = infoWidth / infoHeight;
387-
388-
let maxWidth: number;
389-
let maxHeight: number;
390-
const maxHeightConstraint = forcedHeight || this.props.maxImageHeight || suggestedAndPossibleHeight;
391-
if (maxHeightConstraint * aspectRatio < suggestedAndPossibleWidth || imageSize === ImageSize.Large) {
392-
// The width is dictated by the maximum height that was defined by the props or the function param `forcedHeight`
393-
// If the thumbnail size is set to Large, we always let the size be dictated by the height.
394-
maxWidth = maxHeightConstraint * aspectRatio;
395-
// there is no need to check for infoHeight here since this is done with `maxHeightConstraint * aspectRatio < suggestedAndPossibleWidth`
396-
maxHeight = maxHeightConstraint;
397-
} else {
398-
// height is dictated by suggestedWidth (based on the Image.size setting)
399-
maxWidth = suggestedAndPossibleWidth;
400-
maxHeight = suggestedAndPossibleWidth / aspectRatio;
401-
}
380+
// The maximum size of the thumbnail as it is rendered as an <img>,
381+
// accounting for any height constraints
382+
const { w: maxWidth, h: maxHeight } = suggestedImageSize(
383+
SettingsStore.getValue("Images.size") as ImageSize,
384+
{ w: infoWidth, h: infoHeight },
385+
forcedHeight ?? this.props.maxImageHeight,
386+
);
402387

403388
let img = null;
404389
let placeholder = null;

src/components/views/messages/MVideoBody.tsx

Lines changed: 13 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -62,38 +62,6 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
6262
};
6363
}
6464

65-
private suggestedDimensions(isPortrait): { w: number, h: number } {
66-
return suggestedVideoSize(SettingsStore.getValue("Images.size") as ImageSize);
67-
}
68-
69-
private thumbScale(
70-
fullWidth: number,
71-
fullHeight: number,
72-
thumbWidth?: number,
73-
thumbHeight?: number,
74-
): number {
75-
if (!fullWidth || !fullHeight) {
76-
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
77-
// log this because it's spammy
78-
return undefined;
79-
}
80-
81-
if (!thumbWidth || !thumbHeight) {
82-
const dims = this.suggestedDimensions(fullWidth < fullHeight);
83-
thumbWidth = dims.w;
84-
thumbHeight = dims.h;
85-
}
86-
87-
if (fullWidth < thumbWidth && fullHeight < thumbHeight) {
88-
// no scaling needs to be applied
89-
return 1;
90-
}
91-
92-
// always scale the videos based on their width.
93-
const widthMulti = thumbWidth / fullWidth;
94-
return widthMulti;
95-
}
96-
9765
private getContentUrl(): string|null {
9866
const content = this.props.mxEvent.getContent<IMediaEventContent>();
9967
// During export, the content url will point to the MSC, which will later point to a local url
@@ -135,13 +103,10 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
135103

136104
const canvas = document.createElement("canvas");
137105

138-
let width = info.w;
139-
let height = info.h;
140-
const scale = this.thumbScale(info.w, info.h);
141-
if (scale) {
142-
width = Math.floor(info.w * scale);
143-
height = Math.floor(info.h * scale);
144-
}
106+
const { w: width, h: height } = suggestedVideoSize(
107+
SettingsStore.getValue("Images.size") as ImageSize,
108+
{ w: info.w, h: info.h },
109+
);
145110

146111
canvas.width = width;
147112
canvas.height = height;
@@ -286,24 +251,18 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
286251
);
287252
}
288253

254+
const { w: maxWidth, h: maxHeight } = suggestedVideoSize(
255+
SettingsStore.getValue("Images.size") as ImageSize,
256+
{ w: content.info?.w, h: content.info?.h },
257+
);
258+
289259
const contentUrl = this.getContentUrl();
290260
const thumbUrl = this.getThumbUrl();
291-
const defaultDims = this.suggestedDimensions(false);
292-
let height = defaultDims.h;
293-
let width = defaultDims.w;
294261
let poster = null;
295262
let preload = "metadata";
296-
if (content.info) {
297-
const scale = this.thumbScale(content.info.w, content.info.h);
298-
if (scale) {
299-
width = Math.floor(content.info.w * scale);
300-
height = Math.floor(content.info.h * scale);
301-
}
302-
303-
if (thumbUrl) {
304-
poster = thumbUrl;
305-
preload = "none";
306-
}
263+
if (content.info && thumbUrl) {
264+
poster = thumbUrl;
265+
preload = "none";
307266
}
308267

309268
const fileBody = this.getFileBody();
@@ -318,8 +277,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
318277
preload={preload}
319278
muted={autoplay}
320279
autoPlay={autoplay}
321-
height={height}
322-
width={width}
280+
style={{ maxHeight, maxWidth }}
323281
poster={poster}
324282
onPlay={this.videoOnPlay}
325283
/>

src/settings/enums/ImageSize.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,46 @@ limitations under the License.
1515
*/
1616

1717
// For Large the image gets drawn as big as possible.
18-
// constraint by: timeline width, manual heigh overrides, SIZE_LARGE.h
18+
// constraint by: timeline width, manual height overrides, SIZE_LARGE.h
1919
const SIZE_LARGE = { w: 800, h: 600 };
20-
2120
// For Normal the image gets drawn to never exceed SIZE_NORMAL.w, SIZE_NORMAL.h
22-
// constraint by: timeline width, manual heigh overrides
21+
// constraint by: timeline width, manual height overrides
2322
const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h
2423
const SIZE_NORMAL_PORTRAIT = { w: 324 * (9/16), h: 324 }; // for h > w
24+
25+
type Dimensions = { w: number, h: number };
26+
2527
export enum ImageSize {
2628
Normal = "normal",
2729
Large = "large",
2830
}
2931

30-
export function suggestedSize(size: ImageSize, portrait = false): { w: number, h: number} {
31-
switch (size) {
32-
case ImageSize.Large:
33-
return SIZE_LARGE;
34-
case ImageSize.Normal:
35-
default:
36-
return portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;
32+
/**
33+
* @param {ImageSize} size The user's image size preference
34+
* @param {Dimensions} contentSize The natural dimensions of the content
35+
* @param {number} maxHeight Overrides the default height limit
36+
* @returns {Dimensions} The suggested maximum dimensions for the image
37+
*/
38+
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
39+
const aspectRatio = contentSize.w / contentSize.h;
40+
const portrait = aspectRatio < 1;
41+
42+
const maxSize = (size === ImageSize.Large) ? SIZE_LARGE :
43+
portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;
44+
if (!contentSize.w || !contentSize.h) {
45+
return maxSize;
46+
}
47+
48+
const constrainedSize = {
49+
w: Math.min(maxSize.w, contentSize.w),
50+
h: maxHeight ? Math.min(maxSize.h, contentSize.h, maxHeight) : Math.min(maxSize.h, contentSize.h),
51+
};
52+
53+
if (constrainedSize.h * aspectRatio < constrainedSize.w) {
54+
// Height dictates width
55+
return { w: constrainedSize.h * aspectRatio, h: constrainedSize.h };
56+
} else {
57+
// Width dictates height
58+
return { w: constrainedSize.w, h: constrainedSize.w / aspectRatio };
3759
}
3860
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ImageSize, suggestedSize } from "../../../src/settings/enums/ImageSize";
18+
19+
describe("ImageSize", () => {
20+
describe("suggestedSize", () => {
21+
it("constrains width", () => {
22+
const size = suggestedSize(ImageSize.Normal, { w: 648, h: 162 });
23+
expect(size).toStrictEqual({ w: 324, h: 81 });
24+
});
25+
it("constrains height", () => {
26+
const size = suggestedSize(ImageSize.Normal, { w: 162, h: 648 });
27+
expect(size).toStrictEqual({ w: 81, h: 324 });
28+
});
29+
it("constrains width in large mode", () => {
30+
const size = suggestedSize(ImageSize.Large, { w: 2400, h: 1200 });
31+
expect(size).toStrictEqual({ w: 800, h: 400 });
32+
});
33+
it("returns max values if content size is not specified", () => {
34+
const size = suggestedSize(ImageSize.Normal, { w: null, h: null });
35+
expect(size).toStrictEqual({ w: 324, h: 324 });
36+
});
37+
});
38+
});

0 commit comments

Comments
 (0)