diff --git a/dotcom-rendering/fixtures/manual/trails.ts b/dotcom-rendering/fixtures/manual/trails.ts
index a453a31ff3e..d777c1e2330 100644
--- a/dotcom-rendering/fixtures/manual/trails.ts
+++ b/dotcom-rendering/fixtures/manual/trails.ts
@@ -706,7 +706,10 @@ export const selfHostedLoopVideo54Card = {
height: 400,
},
],
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '5:4',
+ },
duration: 30,
image: 'https://media.guim.co.uk/6537e163c9164d25ec6102641f6a04fa5ba76560/0_210_5472_3283/master/5472.jpg',
},
@@ -730,7 +733,10 @@ export const selfHostedLoopVideo45Card = {
height: 720,
},
],
- aspectRatio: 4 / 5,
+ aspectRatio: {
+ numberRepresentation: 4 / 5,
+ stringRepresentation: '4:5',
+ },
},
} satisfies DCRFrontCard;
@@ -747,7 +753,10 @@ export const selfHostedLoopVideo53Card = {
height: 720,
},
],
- aspectRatio: 5 / 3,
+ aspectRatio: {
+ numberRepresentation: 5 / 3,
+ stringRepresentation: '5:3',
+ },
},
} satisfies DCRFrontCard;
@@ -764,7 +773,10 @@ export const selfHostedLoopVideo916Card = {
height: 720,
},
],
- aspectRatio: 9 / 16,
+ aspectRatio: {
+ numberRepresentation: 9 / 16,
+ stringRepresentation: '9:16',
+ },
},
} satisfies DCRFrontCard;
@@ -781,7 +793,10 @@ export const selfHostedLoopVideo169Card = {
height: 720,
},
],
- aspectRatio: 16 / 9,
+ aspectRatio: {
+ numberRepresentation: 16 / 9,
+ stringRepresentation: '16:9',
+ },
},
} satisfies DCRFrontCard;
diff --git a/dotcom-rendering/src/components/Card/Card.stories.tsx b/dotcom-rendering/src/components/Card/Card.stories.tsx
index b6658c85f2c..00cab2ed51e 100644
--- a/dotcom-rendering/src/components/Card/Card.stories.tsx
+++ b/dotcom-rendering/src/components/Card/Card.stories.tsx
@@ -84,7 +84,7 @@ const mainSelfHostedVideo: MainMedia = {
height: 1080,
},
],
- aspectRatio: 16 / 9,
+ aspectRatio: { numberRepresentation: 16 / 9, stringRepresentation: '16:9' },
image: `https://i.guim.co.uk/img/media/2eb01d138eb8fba6e59ce7589a60e3ff984f6a7a/0_0_1920_1080/1920.jpg?width=1200&quality=45&dpr=2&s=none`,
duration: 100,
};
diff --git a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx
index c89b626dc25..31296acc974 100644
--- a/dotcom-rendering/src/components/SelfHostedVideo.island.tsx
+++ b/dotcom-rendering/src/components/SelfHostedVideo.island.tsx
@@ -25,7 +25,7 @@ import {
} from '../lib/video';
import { palette } from '../palette';
import type { RoleType } from '../types/content';
-import type { VideoPlayerFormat } from '../types/mainMedia';
+import type { AspectRatio, VideoPlayerFormat } from '../types/mainMedia';
import type { RenderingTarget } from '../types/renderingTarget';
import { Caption } from './Caption';
import { CardPicture, type Props as CardPictureProps } from './CardPicture';
@@ -185,7 +185,10 @@ const dispatchOphanAttentionEvent = (
document.dispatchEvent(event);
};
-const getOptimisedPosterImage = (mainImage: string): string => {
+const getOptimisedPosterImage = (
+ mainImage: string,
+ aspectRatio: string,
+): string => {
// This only runs on the client
const resolution = window.devicePixelRatio >= 2 ? 'high' : 'low';
@@ -193,7 +196,7 @@ const getOptimisedPosterImage = (mainImage: string): string => {
mainImage,
imageWidth: 940, // The widest a video can be: flexible special container, giga-boosted slot
resolution,
- aspectRatio: '5:4',
+ aspectRatio,
});
};
@@ -284,7 +287,7 @@ type Props = {
atomId: string;
uniqueId: string;
videoStyle: VideoPlayerFormat;
- aspectRatio: number;
+ aspectRatio: AspectRatio;
posterImage: string;
fallbackImage: CardPictureProps['mainImage'];
fallbackImageSize: CardPictureProps['imageSize'];
@@ -880,13 +883,15 @@ export const SelfHostedVideo = ({
/** The aspect ratio of the video will be clamped within the specified range */
const aspectRatioOfVisibleVideo = getAspectRatioOfVisibleVideo(
- aspectRatio,
+ aspectRatio.numberRepresentation,
minAspectRatio,
maxAspectRatio,
);
- const isVideoCroppedAtTopBottom = aspectRatio < aspectRatioOfVisibleVideo;
- const isVideoCroppedAtLeftRight = aspectRatio > aspectRatioOfVisibleVideo;
+ const isVideoCroppedAtTopBottom =
+ aspectRatio.numberRepresentation < aspectRatioOfVisibleVideo;
+ const isVideoCroppedAtLeftRight =
+ aspectRatio.numberRepresentation > aspectRatioOfVisibleVideo;
const isGreyBarsAtSidesOnDesktop =
containerAspectRatioDesktop !== undefined &&
@@ -899,7 +904,7 @@ export const SelfHostedVideo = ({
const AudioIcon = isMuted ? SvgAudioMute : SvgAudio;
const optimisedPosterImage = showPosterImage
- ? getOptimisedPosterImage(posterImage)
+ ? getOptimisedPosterImage(posterImage, aspectRatio.stringRepresentation)
: undefined;
return (
@@ -922,7 +927,7 @@ export const SelfHostedVideo = ({
>
{
width: 480,
aspectRatio: '5:3',
};
- expect(getAspectRatioFromSources([testSource])).toEqual(5 / 3);
+ expect(getAspectRatioFromSources([testSource])).toEqual({
+ numberRepresentation: 5 / 3,
+ stringRepresentation: '5:3',
+ });
});
it('should calculate the aspect ratio from the width and height if aspect ratio is missing', () => {
@@ -179,7 +182,10 @@ describe('video', () => {
width: 480,
aspectRatio: undefined,
};
- expect(getAspectRatioFromSources([testSource])).toEqual(2 / 3);
+ expect(getAspectRatioFromSources([testSource])).toEqual({
+ numberRepresentation: 2 / 3,
+ stringRepresentation: '480:720',
+ });
});
it('should return the default aspect ratio if the aspect ratio is undefined and width is 0', () => {
@@ -189,7 +195,10 @@ describe('video', () => {
width: 0,
aspectRatio: undefined,
};
- expect(getAspectRatioFromSources([testSource])).toEqual(5 / 4);
+ expect(getAspectRatioFromSources([testSource])).toEqual({
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '5:4',
+ });
});
it('should return the default aspect ratio if the aspect ratio is undefined and height is 0', () => {
@@ -199,7 +208,10 @@ describe('video', () => {
width: 480,
aspectRatio: undefined,
};
- expect(getAspectRatioFromSources([testSource])).toEqual(5 / 4);
+ expect(getAspectRatioFromSources([testSource])).toEqual({
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '5:4',
+ });
});
});
diff --git a/dotcom-rendering/src/lib/video.ts b/dotcom-rendering/src/lib/video.ts
index 7db70f47599..407b74c10c7 100644
--- a/dotcom-rendering/src/lib/video.ts
+++ b/dotcom-rendering/src/lib/video.ts
@@ -4,7 +4,8 @@ import type { VideoAssets } from '../types/content';
export type CustomPlayEventDetail = { uniqueId: string };
/** We expect all videos to include dimensions since the field was added to FEMediaAsset */
-export const DEFAULT_ASPECT_RATIO = 5 / 4;
+const DEFAULT_ASPECT_RATIO_NUMBER = 5 / 4;
+const DEFAULT_ASPECT_RATIO_STRING = '5:4';
export const customSelfHostedVideoPlayAudioEventName =
'self-hosted-video:play-with-audio';
@@ -77,7 +78,9 @@ export const convertFEMediaAssetsToVideoAssets = (
* We use the first source to calculate aspect ratio, but we could use any of the sources.
* We make an assumption that all sources will have the same aspect ratio.
*/
-export const getAspectRatioFromSources = (sources: Source[]): number => {
+export const getAspectRatioFromSources = (
+ sources: Source[],
+): { numberRepresentation: number; stringRepresentation: string } => {
const firstSource = sources[0];
if (firstSource?.aspectRatio !== undefined) {
@@ -88,15 +91,24 @@ export const getAspectRatioFromSources = (sources: Source[]): number => {
width > 0 &&
height > 0
) {
- return width / height;
+ return {
+ numberRepresentation: width / height,
+ stringRepresentation: `${width}:${height}`,
+ };
}
}
if (!firstSource || firstSource.width === 0 || firstSource.height === 0) {
- return DEFAULT_ASPECT_RATIO;
+ return {
+ numberRepresentation: DEFAULT_ASPECT_RATIO_NUMBER,
+ stringRepresentation: DEFAULT_ASPECT_RATIO_STRING,
+ };
}
- return firstSource.width / firstSource.height;
+ return {
+ numberRepresentation: firstSource.width / firstSource.height,
+ stringRepresentation: `${firstSource.width}:${firstSource.height}`,
+ };
};
export const getSubtitleAsset = (assets: VideoAssets[]): string | undefined =>
diff --git a/dotcom-rendering/src/model/enhanceCards.test.ts b/dotcom-rendering/src/model/enhanceCards.test.ts
index a0ecf278b89..8f75fb99b4d 100644
--- a/dotcom-rendering/src/model/enhanceCards.test.ts
+++ b/dotcom-rendering/src/model/enhanceCards.test.ts
@@ -87,7 +87,10 @@ describe('Enhance Cards', () => {
).toEqual({
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
image: '',
type: 'SelfHostedVideo',
videoStyle: 'Loop',
@@ -178,7 +181,10 @@ describe('Enhance Cards', () => {
).toEqual({
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
image: '',
type: 'SelfHostedVideo',
videoStyle: 'Loop',
@@ -225,7 +231,10 @@ describe('Enhance Cards', () => {
).toEqual({
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
image: '',
type: 'SelfHostedVideo',
videoStyle: 'Loop',
@@ -276,7 +285,10 @@ describe('Enhance Cards', () => {
videoStyle: 'Loop',
atomId: 'atomID',
sources: [],
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
duration: 151,
};
@@ -425,7 +437,10 @@ describe('Enhance Cards', () => {
type: 'SelfHostedVideo',
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
image: 'https://guim-example.co.uk/video-image',
sources: [
{
@@ -465,7 +480,10 @@ describe('Enhance Cards', () => {
).toEqual({
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
sources: [
{
mimeType: 'video/mp4',
@@ -490,7 +508,10 @@ describe('Enhance Cards', () => {
type: 'SelfHostedVideo',
atomId: 'atomID',
duration: 15,
- aspectRatio: 5 / 4,
+ aspectRatio: {
+ numberRepresentation: 5 / 4,
+ stringRepresentation: '500:400',
+ },
image: undefined,
sources: [
{
diff --git a/dotcom-rendering/src/types/mainMedia.ts b/dotcom-rendering/src/types/mainMedia.ts
index e586200c7fb..77e14d5b74d 100644
--- a/dotcom-rendering/src/types/mainMedia.ts
+++ b/dotcom-rendering/src/types/mainMedia.ts
@@ -7,6 +7,11 @@ type Media = {
type: 'YoutubeVideo' | 'SelfHostedVideo' | 'Audio' | 'Gallery';
};
+export type AspectRatio = {
+ numberRepresentation: number;
+ stringRepresentation: string;
+};
+
/** For displaying embedded, playable videos directly in cards */
export type YoutubeVideo = Media & {
type: 'YoutubeVideo';
@@ -28,7 +33,7 @@ type SelfHostedVideo = Media & {
videoStyle: VideoPlayerFormat;
atomId: string;
sources: Source[];
- aspectRatio: number;
+ aspectRatio: AspectRatio;
duration: number;
subtitleSource?: string;
image?: string;