diff --git a/apps/web/__tests__/unit/share-video-metadata.test.ts b/apps/web/__tests__/unit/share-video-metadata.test.ts index 376c57cd6bd..2af7bb6af3b 100644 --- a/apps/web/__tests__/unit/share-video-metadata.test.ts +++ b/apps/web/__tests__/unit/share-video-metadata.test.ts @@ -48,6 +48,8 @@ describe("share video metadata", () => { name: "Product demo", sourceType: "desktopMP4", webUrl: "https://cap.so", + width: null, + height: null, advertiseIframelyPlayer: true, }); @@ -84,7 +86,7 @@ describe("share video metadata", () => { rel: "iframely player", url: "https://cap.so/embed/video123", type: "text/html", - media: "(aspect-ratio: 16/9)", + media: "(aspect-ratio: 1280/720)", }, ], }); @@ -99,11 +101,66 @@ describe("share video metadata", () => { name: "Product demo", sourceType: "desktopMP4", webUrl: "https://cap.so", + width: null, + height: null, }); expect(metadata.icons).toBeUndefined(); }); + it.each([ + [1080, 1920], + [1920, 1080], + [1440, 1080], + ])( + "preserves %ix%i recording dimensions in link previews", + (width, height) => { + const metadata = buildShareVideoMetadata({ + videoId: "video123", + name: "Product demo", + sourceType: "desktopMP4", + webUrl: "https://cap.so", + width, + height, + advertiseIframelyPlayer: true, + }); + + expect(metadata.openGraph).toMatchObject({ + videos: [{ width, height }], + }); + expect(metadata.twitter).toMatchObject({ + players: { width, height }, + }); + expect(metadata.icons).toMatchObject({ + other: [{ media: `(aspect-ratio: ${width}/${height})` }], + }); + }, + ); + + it.each([ + [null, null], + [1080, null], + [null, 1920], + [0, 1920], + [1080, 0], + ])("uses default dimensions when %s x %s is incomplete", (width, height) => { + const metadata = buildShareVideoMetadata({ + videoId: "video123", + name: "Product demo", + sourceType: "desktopMP4", + webUrl: "https://cap.so", + width, + height, + }); + + expect(metadata.openGraph).toMatchObject({ + videos: [{ width: 1280, height: 720 }], + }); + expect(metadata.twitter).toMatchObject({ + players: { width: 1280, height: 720 }, + }); + }); + it.each([ ["MediaConvert", "master"], ["local", "master"], diff --git a/apps/web/app/embed/[videoId]/page.tsx b/apps/web/app/embed/[videoId]/page.tsx index bb19036f3bc..0ba5dd33cd4 100644 --- a/apps/web/app/embed/[videoId]/page.tsx +++ b/apps/web/app/embed/[videoId]/page.tsx @@ -53,6 +53,8 @@ export async function generateMetadata( videoId, name: video.name, sourceType: video.source.type, + width: Option.getOrNull(video.width), + height: Option.getOrNull(video.height), webUrl: buildEnv.NEXT_PUBLIC_WEB_URL, }), robots: "index, follow", diff --git a/apps/web/app/s/[videoId]/page.tsx b/apps/web/app/s/[videoId]/page.tsx index d9e6ab94439..d54403b87a4 100644 --- a/apps/web/app/s/[videoId]/page.tsx +++ b/apps/web/app/s/[videoId]/page.tsx @@ -289,6 +289,8 @@ export async function generateMetadata( videoId, name: video.name, sourceType: video.source.type, + width: Option.getOrNull(video.width), + height: Option.getOrNull(video.height), webUrl, canonicalWebUrl: buildEnv.NEXT_PUBLIC_WEB_URL, advertiseIframelyPlayer: shouldAdvertiseIframelyPlayer, diff --git a/apps/web/lib/share-video-metadata.ts b/apps/web/lib/share-video-metadata.ts index 969d25e531a..73fb96cd29b 100644 --- a/apps/web/lib/share-video-metadata.ts +++ b/apps/web/lib/share-video-metadata.ts @@ -15,6 +15,8 @@ export type ShareVideoMetadataInput = { name: string; sourceType: ShareVideoSourceType; webUrl: string; + width: number | null; + height: number | null; /** * Two surfaces only answer on the default Cap origin: `proxy.ts` redirects * `/embed/` away from a custom domain, and `parseCapShareUrl` accepts share @@ -79,6 +81,8 @@ export const buildShareVideoMetadata = ({ webUrl, canonicalWebUrl, advertiseIframelyPlayer = false, + width, + height, }: ShareVideoMetadataInput): Metadata => { const urls = getShareVideoUrls({ videoId, @@ -88,6 +92,10 @@ export const buildShareVideoMetadata = ({ }); const title = `${name} | Cap Recording`; const description = "Watch this video on Cap"; + const hasDimensions = + width != null && height != null && width > 0 && height > 0; + const playerWidth = hasDimensions ? width : PLAYER_WIDTH; + const playerHeight = hasDimensions ? height : PLAYER_HEIGHT; return { title, @@ -100,7 +108,7 @@ export const buildShareVideoMetadata = ({ rel: "iframely player", url: urls.playerUrl, type: "text/html", - media: "(aspect-ratio: 16/9)", + media: `(aspect-ratio: ${playerWidth}/${playerHeight})`, }, ], }, @@ -142,8 +150,8 @@ export const buildShareVideoMetadata = ({ { url: urls.streamUrl, secureUrl: urls.streamUrl, - width: PLAYER_WIDTH, - height: PLAYER_HEIGHT, + width: playerWidth, + height: playerHeight, type: urls.streamContentType, }, ], @@ -156,8 +164,8 @@ export const buildShareVideoMetadata = ({ players: { playerUrl: urls.playerUrl, streamUrl: urls.streamUrl, - width: PLAYER_WIDTH, - height: PLAYER_HEIGHT, + width: playerWidth, + height: playerHeight, }, }, other: {