Skip to content

Commit

Permalink
Merge branch 'latest' into revert-11261-revert-11122-remove-nodefetch
Browse files Browse the repository at this point in the history
  • Loading branch information
amoore108 authored Mar 20, 2024
2 parents 5c92513 + a372da4 commit 33d8241
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 12 deletions.
2 changes: 0 additions & 2 deletions src/app/components/Ad/Canonical/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const CanonicalAd = ({ slotType, className }: AdProps) => {
const ariaLabel = getAdsAriaLabel({ label, dir, slotType });

useEffect(() => {
// @ts-expect-error dotcom is added to the window object by BBC Ads script
if (window.dotcom && location.href != null) {
// @ts-expect-error dotcom is added to the window object by BBC Ads script
window.dotcom.cmd.push(() => {
Expand All @@ -57,7 +56,6 @@ const CanonicalAd = ({ slotType, className }: AdProps) => {
}

return () => {
// @ts-expect-error dotcom is added to the window object by BBC Ads script
if (window.dotcom) {
// @ts-expect-error dotcom is added to the window object by BBC Ads script
window.dotcom.cmd.push(() => {
Expand Down
17 changes: 16 additions & 1 deletion src/app/components/MediaLoader/configs/aresMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import {
AresMediaBlock,
ConfigBuilderProps,
ConfigBuilderReturnProps,
PlaylistItem,
} from '../types';
import getCaptionBlock from '../utils/getCaptionBlock';

const DEFAULT_WIDTH = 512;
const MIN_DURATION_FOR_PREROLLS = 30;

export default ({
pageType,
blocks,
basePlayerConfig,
translations,
adsEnabled = false,
showAdsBasedOnLocation = false,
}: ConfigBuilderProps): ConfigBuilderReturnProps => {
const aresMediaBlock: AresMediaBlock = filterForBlockType(
blocks,
Expand Down Expand Up @@ -75,6 +79,13 @@ export default ({
guidanceMessage,
};

const allowAdsForVideoDuration = rawDuration >= MIN_DURATION_FOR_PREROLLS;
const showAds = [
adsEnabled,
showAdsBasedOnLocation,
allowAdsForVideoDuration,
].every(Boolean);

const embeddingAllowed =
aresMediaBlock?.model?.blocks?.[0]?.model?.embedding ?? false;

Expand All @@ -92,6 +103,9 @@ export default ({

const noJsMessage = `This ${mediaInfo.type} cannot play in your browser. Please enable JavaScript or try a different browser.`;

const items = [{ versionID, kind, duration: rawDuration }];
if (showAds) items.unshift({ kind: 'advert' } as PlaylistItem);

return {
mediaType: format || 'video',
playerConfig: {
Expand All @@ -101,7 +115,7 @@ export default ({
title,
summary: caption || '',
holdingImageURL: placeholderSrc,
items: [{ versionID, kind, duration: rawDuration }],
items,
...(guidanceMessage && { guidance: guidanceMessage }),
...(embeddingAllowed && { embedRights: 'allowed' }),
},
Expand All @@ -117,5 +131,6 @@ export default ({
placeholderSrcset,
translatedNoJSMessage: noJsMessage,
},
showAds,
};
};
17 changes: 16 additions & 1 deletion src/app/components/MediaLoader/configs/clipMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import {
ClipMediaBlock,
ConfigBuilderProps,
ConfigBuilderReturnProps,
PlaylistItem,
} from '../types';
import getCaptionBlock from '../utils/getCaptionBlock';

const DEFAULT_WIDTH = 512;
const MIN_DURATION_FOR_PREROLLS = 30;

export default ({
blocks,
basePlayerConfig,
translations,
adsEnabled = false,
showAdsBasedOnLocation = false,
}: ConfigBuilderProps): ConfigBuilderReturnProps => {
const clipMediaBlock: ClipMediaBlock = filterForBlockType(
blocks,
Expand Down Expand Up @@ -63,6 +67,13 @@ export default ({
guidanceMessage,
};

const allowAdsForVideoDuration = rawDuration >= MIN_DURATION_FOR_PREROLLS;
const showAds = [
adsEnabled,
showAdsBasedOnLocation,
allowAdsForVideoDuration,
].every(Boolean);

const embeddingAllowed = video?.isEmbeddingAllowed ?? false;

const placeholderSrc = buildIChefURL({
Expand All @@ -89,6 +100,9 @@ export default ({
controls: { enabled: true, volumeSlider: true },
};

const items = [{ versionID, kind, duration: rawDuration }];
if (showAds) items.unshift({ kind: 'advert' } as PlaylistItem);

return {
mediaType: type || 'video',
playerConfig: {
Expand All @@ -97,7 +111,7 @@ export default ({
title,
summary: caption || '',
holdingImageURL: placeholderSrc,
items: [{ versionID, kind, duration: rawDuration }],
items,
...(guidanceMessage && { guidance: guidanceMessage }),
...(embeddingAllowed && { embedRights: 'allowed' }),
},
Expand All @@ -116,5 +130,6 @@ export default ({
placeholderSrcset,
translatedNoJSMessage: noJsMessage,
},
showAds,
};
};
37 changes: 36 additions & 1 deletion src/app/components/MediaLoader/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { act } from '@testing-library/react-hooks';
import { Helmet } from 'react-helmet';
import useLocation from '#app/hooks/useLocation';
import MediaPlayer from '.';
import { aresMediaBlocks } from './fixture';
import { render } from '../react-testing-library-with-providers';
Expand All @@ -11,16 +12,50 @@ jest.mock('react', () => ({
useState: jest.fn(),
}));

jest.mock('#app/hooks/useLocation');

describe('MediaLoader', () => {
describe('BUMP Loader', () => {
beforeEach(() => {
jest.restoreAllMocks();
// @ts-expect-error Mocking require to prevent race condition.
window.require = jest.fn();
(useLocation as jest.Mock).mockImplementation(() => ({ search: '' }));
(useState as jest.Mock).mockImplementation(() => [false, () => false]);
});

it('Loads requireJS and Bump4', async () => {
it('Loads Ads, requireJS and Bump4 when Ads are enabled', async () => {
await act(async () => {
render(<MediaPlayer blocks={aresMediaBlocks as MediaBlock[]} />, {
id: 'testId',
showAdsBasedOnLocation: true,
toggles: { ads: { enabled: true } },
});
});

const adScript = Helmet.peek().scriptTags[0];
const adScriptLegacy = Helmet.peek().scriptTags[1];
const requireScript = Helmet.peek().scriptTags[2];
const bumpScript = Helmet.peek().scriptTags[3];

expect(adScript.src).toEqual(
'https://gn-web-assets.api.bbc.com/ngas/latest/test/dotcom-bootstrap.js',
);

expect(adScriptLegacy.src).toEqual(
'https://gn-web-assets.api.bbc.com/ngas/latest/test/dotcom-bootstrap-legacy.js',
);

expect(requireScript.src).toEqual(
'https://static.bbci.co.uk/frameworks/requirejs/0.13.0/sharedmodules/require.js',
);

expect(bumpScript.innerHTML).toContain(
'https://emp.bbci.co.uk/emp/bump-4/bump-4',
);
});

it('Loads requireJS and Bump4 when Ads are disabled', async () => {
await act(async () => {
render(<MediaPlayer blocks={aresMediaBlocks as MediaBlock[]} />, {
id: 'testId',
Expand Down
105 changes: 98 additions & 7 deletions src/app/components/MediaLoader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Helmet } from 'react-helmet';
import { RequestContext } from '#contexts/RequestContext';
import { MEDIA_PLAYER_STATUS } from '#app/lib/logger.const';
import { ServiceContext } from '#app/contexts/ServiceContext';
import useLocation from '#app/hooks/useLocation';
import useToggle from '#app/hooks/useToggle';
import { BumpType, MediaBlock, PlayerConfig } from './types';
import Caption from '../Caption';
import nodeLogger from '../../lib/logger.node';
Expand All @@ -13,6 +15,7 @@ import Placeholder from './Placeholder';
import getProducerFromServiceName from './utils/getProducerFromServiceName';
import getCaptionBlock from './utils/getCaptionBlock';
import styles from './index.styles';
import { getBootstrapSrc } from '../Ad/Canonical';

const logger = nodeLogger(__filename);

Expand All @@ -31,25 +34,101 @@ const BumpLoader = () => (
</Helmet>
);

const MediaContainer = ({ playerConfig }: { playerConfig: PlayerConfig }) => {
const AdvertTagLoader = () => {
const location = useLocation();
const queryString = location ? location.search : '';

useEffect(() => {
// Set window.dotcom to disabled if it doesn't load in 2 seconds.
const timeoutID = setTimeout(() => {
if (window.dotcom.ads.resolves) {
window.dotcom.ads.resolves.enabled.forEach(res => res(false));
window.dotcom.ads.resolves.getAdTag.forEach(res => res(''));
}
}, 2000);

// Initialise the ads object if it hasn't already been loaded.
window.dotcom = window.dotcom || { cmd: [] };
window.dotcom.ads = window.dotcom.ads || {
resolves: {
enabled: [],
getAdTag: [],
},
enabled() {
return new Promise(resolve => {
window.dotcom.ads.resolves.enabled.push(resolve);
});
},
getAdTag() {
return new Promise(resolve => {
window.dotcom.ads.resolves.getAdTag.push(resolve);
});
},
};

return () => clearTimeout(timeoutID);
}, [queryString]);

return (
<Helmet>
<script type="module" src={getBootstrapSrc(queryString)} async />
<script noModule src={getBootstrapSrc(queryString, true)} async />
</Helmet>
);
};

const MediaContainer = ({
playerConfig,
showAds,
}: {
playerConfig: PlayerConfig;
showAds: boolean;
}) => {
const playerElementRef = useRef<HTMLDivElement>(null);

useEffect(() => {
try {
window.requirejs(['bump-4'], (Bump: BumpType) => {
window.requirejs(['bump-4'], async (Bump: BumpType) => {
if (playerElementRef?.current && playerConfig) {
const mediaPlayer = Bump.player(
playerElementRef.current,
playerConfig,
);

mediaPlayer.load();

if (showAds) {
const adTag = await window.dotcom.ads.getAdTag();
mediaPlayer.loadPlugin(
{
swf: 'name:dfpAds.swf',
html: 'name:dfpAds.js',
},
{
name: 'AdsPluginParameters',
data: {
adTag,
debug: true,
},
},
);

mediaPlayer.bind('playlistLoaded', async () => {
const updatedAdTag = await window.dotcom.ads.getAdTag();
mediaPlayer.dispatchEvent(
'bbc.smp.plugins.ads.event.updateAdTag',
{
updatedAdTag,
},
);
});
}
}
});
} catch (error) {
logger.error(MEDIA_PLAYER_STATUS, error);
}
}, [playerConfig]);
}, [playerConfig, showAds]);

return (
<div
Expand All @@ -67,9 +146,18 @@ type Props = {

const MediaLoader = ({ blocks, className }: Props) => {
const [isPlaceholder, setIsPlaceholder] = useState(true);
const { id, pageType, counterName, statsDestination, service, isAmp } =
useContext(RequestContext);
const { lang, translations } = useContext(ServiceContext);
const { enabled: adsEnabled } = useToggle('ads');

const {
id,
pageType,
counterName,
statsDestination,
service,
isAmp,
showAdsBasedOnLocation,
} = useContext(RequestContext);

const producer = getProducerFromServiceName(service);

Expand All @@ -84,11 +172,13 @@ const MediaLoader = ({ blocks, className }: Props) => {
pageType,
service,
translations,
adsEnabled,
showAdsBasedOnLocation,
});

if (!config) return null;

const { mediaType, playerConfig, placeholderConfig } = config;
const { mediaType, playerConfig, placeholderConfig, showAds } = config;

const {
mediaInfo,
Expand All @@ -105,6 +195,7 @@ const MediaLoader = ({ blocks, className }: Props) => {
css={styles.figure}
className={className}
>
{showAds && <AdvertTagLoader />}
<BumpLoader />
{isPlaceholder ? (
<Placeholder
Expand All @@ -115,7 +206,7 @@ const MediaLoader = ({ blocks, className }: Props) => {
onClick={() => setIsPlaceholder(false)}
/>
) : (
<MediaContainer playerConfig={playerConfig} />
<MediaContainer playerConfig={playerConfig} showAds={showAds} />
)}
{captionBlock && <Caption block={captionBlock} type={mediaType} />}
</figure>
Expand Down
Loading

0 comments on commit 33d8241

Please sign in to comment.