Skip to content

Commit 522e0e7

Browse files
authored
Merge pull request #15545 from guardian/doml/sh-video-multiple-mp4s
Support multiple self-hosted videos per MIME type
2 parents 5d27083 + b9affee commit 522e0e7

3 files changed

Lines changed: 122 additions & 134 deletions

File tree

dotcom-rendering/src/components/SelfHostedVideo.stories.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { breakpoints } from '@guardian/source/foundations';
1+
import { breakpoints, textSans17Object } from '@guardian/source/foundations';
22
import type { Meta, StoryObj } from '@storybook/react-webpack5';
33
import { expect, userEvent, within } from 'storybook/test';
44
import { centreColumnDecorator } from '../../.storybook/decorators/gridDecorators';
@@ -8,7 +8,23 @@ const meta = {
88
component: SelfHostedVideo,
99
title: 'Components/SelfHostedVideo',
1010
decorators: [centreColumnDecorator],
11-
render: (args) => <SelfHostedVideo {...args} />,
11+
render: (args) => (
12+
<div
13+
style={{
14+
display: 'flex',
15+
flexDirection: 'column',
16+
gap: '20px',
17+
marginTop: '20px',
18+
}}
19+
>
20+
<p style={{ ...textSans17Object }}>
21+
Note: If the video is not playing, you may need to visit
22+
https://storybook.thegulocal.com/ instead of localhost, as CORS
23+
is enabled for self-hosted video.
24+
</p>
25+
<SelfHostedVideo {...args} />,
26+
</div>
27+
),
1228
parameters: {
1329
chromatic: {
1430
viewports: [breakpoints.mobile, breakpoints.wide],

dotcom-rendering/src/model/enhanceCards.test.ts

Lines changed: 94 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,54 @@ import {
99
} from './enhanceCards';
1010

1111
describe('Enhance Cards', () => {
12+
const testMp4Asset: FEMediaAsset = {
13+
id: 'https://guim-example.co.uk/atomID-1.mp4',
14+
version: 1,
15+
platform: 'Url',
16+
mimeType: 'video/mp4',
17+
assetType: 'Video',
18+
dimensions: {
19+
height: 400,
20+
width: 500,
21+
},
22+
};
23+
24+
const testSubtitleAsset: FEMediaAsset = {
25+
id: 'https://guim-example.co.uk/atomID-1.vtt',
26+
version: 1,
27+
platform: 'Url',
28+
mimeType: 'text/vtt',
29+
assetType: 'Subtitles',
30+
};
31+
32+
const testM3u8Asset: FEMediaAsset = {
33+
id: 'https://guim-example.co.uk/atomID-1.m3u8',
34+
version: 1,
35+
platform: 'Url',
36+
mimeType: 'application/x-mpegURL',
37+
assetType: 'Video',
38+
dimensions: {
39+
height: 400,
40+
width: 500,
41+
},
42+
};
43+
44+
const testMediaAtom: FEMediaAtom = {
45+
id: 'atomID',
46+
assets: [testMp4Asset, testM3u8Asset],
47+
title: 'Example video',
48+
duration: 15,
49+
source: '',
50+
posterImage: { allImages: [] },
51+
trailImage: { allImages: [] },
52+
expired: false,
53+
activeVersion: 1,
54+
};
55+
1256
describe('getActiveMediaAtom', () => {
1357
it('prioritises MP4 assets over m3u8 assets', () => {
1458
const videoReplace = true;
15-
const assets: FEMediaAsset[] = [
16-
{
17-
id: 'https://guim-example.co.uk/atomID-1.mp4',
18-
version: 1,
19-
platform: 'Url',
20-
mimeType: 'video/mp4',
21-
assetType: 'Video',
22-
dimensions: {
23-
height: 400,
24-
width: 500,
25-
},
26-
},
27-
{
28-
id: 'https://guim-example.co.uk/atomID-1.m3u8',
29-
version: 1,
30-
platform: 'Url',
31-
mimeType: 'application/x-mpegURL',
32-
assetType: 'Video',
33-
dimensions: {
34-
height: 400,
35-
width: 500,
36-
},
37-
},
38-
];
39-
const mediaAtom: FEMediaAtom = {
40-
id: 'atomID',
41-
assets,
42-
title: 'Example video',
43-
duration: 15,
44-
source: '',
45-
posterImage: { allImages: [] },
46-
trailImage: { allImages: [] },
47-
expired: false,
48-
activeVersion: 1,
49-
};
59+
const mediaAtom = testMediaAtom;
5060
const cardTrailImage = '';
5161

5262
expect(
@@ -73,49 +83,50 @@ describe('Enhance Cards', () => {
7383
});
7484
});
7585

76-
it('filters out non-video assets', () => {
86+
it('returns the larger of two MP4 assets', () => {
7787
const videoReplace = true;
78-
const assets: FEMediaAsset[] = [
79-
{
80-
id: 'https://guim-example.co.uk/atomID-1.vtt',
81-
version: 1,
82-
platform: 'Url',
83-
mimeType: 'text/vtt',
84-
assetType: 'Subtitles',
85-
},
86-
{
87-
id: 'https://guim-example.co.uk/atomID-1.m3u8',
88-
version: 1,
89-
platform: 'Url',
90-
mimeType: 'application/x-mpegURL',
91-
assetType: 'Video',
92-
dimensions: {
93-
height: 400,
94-
width: 500,
88+
const mediaAtom: FEMediaAtom = {
89+
...testMediaAtom,
90+
assets: [
91+
{
92+
...testMp4Asset,
93+
dimensions: { height: 400, width: 500 },
94+
id: 'https://guim-example.co.uk/atomID-1.mp4',
9595
},
96-
},
97-
{
98-
id: 'https://guim-example.co.uk/atomID-1.mp4',
99-
version: 1,
100-
platform: 'Url',
101-
mimeType: 'video/mp4',
102-
assetType: 'Video',
103-
dimensions: {
104-
height: 400,
105-
width: 500,
96+
{
97+
...testMp4Asset,
98+
dimensions: { height: 600, width: 750 },
99+
id: 'https://guim-example.co.uk/atomID-2.mp4',
106100
},
107-
},
108-
];
109-
const mediaAtom: FEMediaAtom = {
110-
id: 'atomID',
111-
assets,
112-
title: 'Example video',
101+
],
102+
};
103+
const cardTrailImage = '';
104+
105+
expect(
106+
getActiveMediaAtom(videoReplace, mediaAtom, cardTrailImage),
107+
).toEqual({
108+
atomId: 'atomID',
113109
duration: 15,
114-
source: '',
115-
posterImage: { allImages: [] },
116-
trailImage: { allImages: [] },
117-
expired: false,
118-
activeVersion: 1,
110+
height: 400,
111+
width: 500,
112+
image: '',
113+
type: 'SelfHostedVideo',
114+
videoStyle: 'Loop',
115+
subtitleSource: undefined,
116+
sources: [
117+
{
118+
mimeType: 'video/mp4',
119+
src: 'https://guim-example.co.uk/atomID-2.mp4',
120+
},
121+
],
122+
});
123+
});
124+
125+
it('filters out non-video assets', () => {
126+
const videoReplace = true;
127+
const mediaAtom: FEMediaAtom = {
128+
...testMediaAtom,
129+
assets: [testSubtitleAsset, testM3u8Asset, testMp4Asset],
119130
};
120131
const cardTrailImage = '';
121132

@@ -146,7 +157,7 @@ describe('Enhance Cards', () => {
146157

147158
describe('getMediaMetadata', () => {
148159
it('extracts type, duration, and live status from a YouTube video media object', () => {
149-
const media: MainMedia = {
160+
const testYoutubeMainMedia: MainMedia = {
150161
type: 'YoutubeVideo',
151162
id: 'atomID',
152163
videoId: 'videoID',
@@ -159,15 +170,15 @@ describe('Enhance Cards', () => {
159170
isLive: false,
160171
};
161172

162-
expect(getMediaMetadata(media)).toEqual({
173+
expect(getMediaMetadata(testYoutubeMainMedia)).toEqual({
163174
type: 'YoutubeVideo',
164175
duration: 151,
165176
isLive: false,
166177
});
167178
});
168179

169180
it('extracts type and duration from a self-hosted video media object', () => {
170-
const media: MainMedia = {
181+
const testSelfHostedMainMedia: MainMedia = {
171182
type: 'SelfHostedVideo',
172183
videoStyle: 'Loop',
173184
atomId: 'atomID',
@@ -177,7 +188,7 @@ describe('Enhance Cards', () => {
177188
duration: 151,
178189
};
179190

180-
expect(getMediaMetadata(media)).toEqual({
191+
expect(getMediaMetadata(testSelfHostedMainMedia)).toEqual({
181192
type: 'SelfHostedVideo',
182193
duration: 151,
183194
});
@@ -305,19 +316,7 @@ describe('Enhance Cards', () => {
305316

306317
const mediaAtom: FEMediaAtom = {
307318
id: 'atomID',
308-
assets: [
309-
{
310-
id: 'https://guim-example.co.uk/atomID-1.mp4',
311-
version: 1,
312-
platform: 'Url',
313-
mimeType: 'video/mp4',
314-
assetType: 'Video',
315-
dimensions: {
316-
height: 400,
317-
width: 500,
318-
},
319-
},
320-
],
319+
assets: [testMp4Asset],
321320
title: 'Example video',
322321
duration: 15,
323322
source: '',
@@ -360,19 +359,7 @@ describe('Enhance Cards', () => {
360359
it('returns undefined if a mediaAtom is provided but showMainVideo and videoReplace are both false', () => {
361360
const mediaAtom: FEMediaAtom = {
362361
id: 'atomID',
363-
assets: [
364-
{
365-
id: 'https://guim-example.co.uk/atomID-1.mp4',
366-
version: 1,
367-
platform: 'Url',
368-
mimeType: 'video/mp4',
369-
assetType: 'Video',
370-
dimensions: {
371-
height: 400,
372-
width: 500,
373-
},
374-
},
375-
],
362+
assets: [testMp4Asset],
376363
title: 'Example video',
377364
duration: 15,
378365
source: '',
@@ -391,19 +378,7 @@ describe('Enhance Cards', () => {
391378
it('returns a video main media if a mediaAtom is provided and showMainVideo is set to true', () => {
392379
const mediaAtom: FEMediaAtom = {
393380
id: 'atomID',
394-
assets: [
395-
{
396-
id: 'https://guim-example.co.uk/atomID-1.mp4',
397-
version: 1,
398-
platform: 'Url',
399-
mimeType: 'video/mp4',
400-
assetType: 'Video',
401-
dimensions: {
402-
height: 400,
403-
width: 500,
404-
},
405-
},
406-
],
381+
assets: [testMp4Asset],
407382
title: 'Example video',
408383
duration: 15,
409384
source: '',
@@ -432,22 +407,11 @@ describe('Enhance Cards', () => {
432407
width: 500,
433408
});
434409
});
410+
435411
it('returns a video main media if a mediaAtom is provided and videoReplace is set to true', () => {
436412
const mediaAtom: FEMediaAtom = {
437413
id: 'atomID',
438-
assets: [
439-
{
440-
id: 'https://guim-example.co.uk/atomID-1.mp4',
441-
version: 1,
442-
platform: 'Url',
443-
mimeType: 'video/mp4',
444-
assetType: 'Video',
445-
dimensions: {
446-
height: 400,
447-
width: 500,
448-
},
449-
},
450-
],
414+
assets: [testMp4Asset],
451415
title: 'Example video',
452416
duration: 15,
453417
source: '',

dotcom-rendering/src/model/enhanceCards.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,20 @@ export const getActiveMediaAtom = (
222222
* Therefore, we check the platform of the first asset and assume the rest are the same.
223223
*/
224224
if (firstVideoAsset.platform === 'Url') {
225+
// Order the assets by largest width: for now, we only use the largest video, but there
226+
// be a follow up PR to select the appropriate video source based on the users screen size.
227+
const orderedSources = assets.sort(
228+
(a, b) =>
229+
Number(b.dimensions?.width ?? 0) -
230+
Number(a.dimensions?.width ?? 0),
231+
);
232+
225233
/**
226234
* Take one source for each supported video file type.
227235
*/
228236
const sources = supportedVideoFileTypes.reduce<typeof assets>(
229237
(acc, type) => {
230-
const source = assets.find(
238+
const source = orderedSources.find(
231239
({ mimeType }) => mimeType === type,
232240
);
233241
if (source) acc.push(source);
@@ -258,7 +266,7 @@ export const getActiveMediaAtom = (
258266
}
259267

260268
/**
261-
* There should only be one asset for Youtube atoms.
269+
* There should only be one asset for Youtube atoms, so we use the first one.
262270
*/
263271
if (firstVideoAsset.platform === 'Youtube') {
264272
return {

0 commit comments

Comments
 (0)