diff --git a/dotcom-rendering/src/components/LoopVideo.importable.tsx b/dotcom-rendering/src/components/LoopVideo.importable.tsx
index 2b80188a216..8871bf056a2 100644
--- a/dotcom-rendering/src/components/LoopVideo.importable.tsx
+++ b/dotcom-rendering/src/components/LoopVideo.importable.tsx
@@ -67,8 +67,8 @@ const dispatchOphanAttentionEvent = (
document.dispatchEvent(event);
};
-const getOptimisedPosterImage = (mainImage: string): string => {
- const resolution = window.devicePixelRatio >= 2 ? 'high' : 'low';
+const getOptimisedPosterImage = (mainImage: string, dpr: number): string => {
+ const resolution = dpr >= 2 ? 'high' : 'low';
return generateImageURL({
mainImage,
@@ -168,6 +168,8 @@ export const LoopVideo = ({
const [hasBeenPlayed, setHasBeenPlayed] = useState(false);
const [hasTrackedPlay, setHasTrackedPlay] = useState(false);
+ const [devicePixelRatio, setDevicePixelRatio] = useState(1);
+
const VISIBILITY_THRESHOLD = 0.5;
const [isInView, setNode] = useIsInView({
@@ -337,6 +339,8 @@ export const LoopVideo = ({
}
});
+ setDevicePixelRatio(window.devicePixelRatio);
+
return () => {
document.removeEventListener(
customLoopPlayAudioEventName,
@@ -636,7 +640,7 @@ export const LoopVideo = ({
const AudioIcon = isMuted ? SvgAudioMute : SvgAudio;
const optimisedPosterImage = showPosterImage
- ? getOptimisedPosterImage(posterImage)
+ ? getOptimisedPosterImage(posterImage, devicePixelRatio)
: undefined;
return (
diff --git a/dotcom-rendering/src/components/LoopVideoInArticle.importable.tsx b/dotcom-rendering/src/components/LoopVideoInArticle.importable.tsx
new file mode 100644
index 00000000000..5295b989be7
--- /dev/null
+++ b/dotcom-rendering/src/components/LoopVideoInArticle.importable.tsx
@@ -0,0 +1,91 @@
+import type { ArticleFormat } from '../lib/articleFormat';
+import type { Source } from '../lib/video';
+import type { VideoAssets } from '../types/content';
+import { Caption } from './Caption';
+import type { Props as CardPictureProps } from './CardPicture';
+import { LoopVideo } from './LoopVideo.importable';
+import type { SubtitleSize } from './LoopVideoPlayer';
+
+type LoopVideoInArticleProps = {
+ assets: VideoAssets[];
+ atomId: string;
+ caption?: string;
+ fallbackImage: CardPictureProps['mainImage'];
+ fallbackImageAlt: CardPictureProps['alt'];
+ fallbackImageAspectRatio: CardPictureProps['aspectRatio'];
+ fallbackImageLoading: CardPictureProps['loading'];
+ fallbackImageSize: CardPictureProps['imageSize'];
+ format: ArticleFormat;
+ height: number;
+ isMainMedia: boolean;
+ linkTo: string;
+ posterImage: string;
+ uniqueId: string;
+ width: number;
+};
+
+// The looping video player types its `sources` attribute as `Sources`
+// However, looping videos in articles are delivered as media atoms, which type their `assets` as `VideoAssets`
+// Which means that we need to alter the shape of the incoming `assets` to match the requirements of the outgoing `sources`
+const convertAssetsToSources = (assets: VideoAssets[]): Source[] => {
+ return assets.map((asset) => {
+ return {
+ src: asset.url ?? '',
+ mimeType: 'video/mp4',
+ };
+ });
+};
+
+const getSubtitleAsset = (assets: VideoAssets[]) => {
+ // Get the first subtitle asset from assets with a mimetype of 'text/vtt'
+
+ const candidate = assets.find((asset) => asset.mimeType === 'text/vtt');
+ return candidate?.url;
+};
+
+export const LoopVideoInArticle = ({
+ assets,
+ atomId,
+ caption,
+ fallbackImage,
+ fallbackImageAlt,
+ fallbackImageAspectRatio,
+ fallbackImageLoading,
+ fallbackImageSize,
+ format,
+ height = 400,
+ isMainMedia,
+ linkTo,
+ posterImage,
+ uniqueId,
+ width = 500,
+}: LoopVideoInArticleProps) => {
+ return (
+ <>
+