Skip to main content

MediaEmbed

MediaEmbed

Lazy YouTube, Vimeo, Cloudflare Stream, and Matterport embeds that load nothing until clicked

Tags: component media display

View Demo

MediaEmbed shows a poster image and a play button, and only injects the provider's iframe once the viewer clicks it. Until then the page loads none of the provider's player code, which keeps it light and sets no tracking cookies on arrival.

When the poster is derived for YouTube, the image itself is still fetched from i.ytimg.com — a static image host, not the player. Pass your own poster to avoid contacting the provider at all before playback.

Import​

<script>
  import { MediaEmbed } from '@classic-homes/theme-svelte';
</script>

Basic Usage​

<MediaEmbed url="https://youtu.be/dQw4w9WgXcQ" title="Never Gonna Give You Up" />

Props​

Prop Type Default Description
url string - Full provider URL; ignored when provider and videoId are set
provider 'youtube' | 'vimeo' | 'cloudflare' | 'matterport' - Hosting provider
videoId string - Provider-specific video identifier
title string required Accessible name for the player; required
poster string - Poster image; derived for YouTube and Stream, required for Vimeo
privacy boolean true Route YouTube through the no-cookie domain
start number - Start offset in seconds
showRelated boolean false Show the provider's related-video suggestions
customerCode string - Cloudflare Stream customer code; required for that provider
domain string - Custom hostname serving Cloudflare Stream
primaryColor string - Accent colour for the Cloudflare player UI
letterboxColor string - Letterbox colour for the Cloudflare player
defaultTextTrack string - BCP-47 code of the captions track the Cloudflare player enables
modelId string - Matterport space id; an alias for videoId
matterport MatterportPlayerOptions - Showcase options for the Matterport player
fetchMetadata boolean false Look up the space's preview image and name from Matterport
ratio 'square' | 'video' | 'photo' | 'portrait' | 'wide' | number 'video' Aspect ratio of the embed box
eager boolean false Load the iframe immediately instead of showing a facade
class string - Additional CSS classes

Supported URL Formats​

url accepts every common share and watch link, including the start offset:

https://www.youtube.com/watch?v=VIDEO_ID
https://youtu.be/VIDEO_ID?t=30
https://www.youtube.com/embed/VIDEO_ID
https://www.youtube.com/shorts/VIDEO_ID
https://www.youtube.com/live/VIDEO_ID
https://vimeo.com/VIDEO_ID
https://player.vimeo.com/video/VIDEO_ID
https://customer-CODE.cloudflarestream.com/VIDEO_UID/iframe
https://customer-CODE.cloudflarestream.com/VIDEO_UID/watch
https://iframe.videodelivery.net/VIDEO_UID
https://my.matterport.com/show/?m=MODEL_ID

An unrecognised URL renders an inline error rather than an empty frame, and warns in development.

Cloudflare Stream​

Cloudflare Stream needs the account as well as the video, either as customerCode or as part of a full URL:

<MediaEmbed
  provider="cloudflare"
  videoId="a1b2c3d4e5f6"
  customerCode="abc123"
  title="Product tour"
  primaryColor="#c8102e"
/>

The poster is derived from Stream's URL scheme, so the facade needs no poster and no API call. A signed playback token works in place of the video id for private videos.

This embeds Cloudflare's own player, which brings its own controls, adaptive bitrate, and analytics. To play a Stream video with this design system's controls instead, use Video with cloudflareStream().

Matterport 3D Tours​

Matterport embeds an interactive 3D space rather than a video, so it has no timeline: tour tiles show a 3D affordance instead of a play button, and never a duration.

<MediaEmbed
  provider="matterport"
  modelId="SxQL3iGyoDo"
  title="Show home tour"
  matterport={{ brand: false, highlightReel: false }}
/>

A full showcase URL works too, and the space id is read from it:

<MediaEmbed url="https://my.matterport.com/show/?m=SxQL3iGyoDo" title="Show home tour" />

For galleries, matterportTour() builds an item that MediaGrid, MediaCarousel, and Lightbox all understand:

<script lang="ts">
  import { MediaGrid, matterportTour } from '@classic-homes/theme-svelte';

  const tours = [matterportTour('SxQL3iGyoDo', { title: 'Show home', caption: 'Ground floor' })];
</script>

<MediaGrid items={tours} label="Property tours" />

Showcase options​

Passed as matterport={{ … }}. Matterport's own defaults are left alone unless an option asks otherwise, so a tour looks the way its owner configured it.

Option Type Default Showcase parameter
quickStart boolean true qs — reach an explorable view sooner
autoplay boolean false play — start moving on load
brand boolean true brand — "Presented by" and contact details
help boolean true help — in-player help button
highlightReel boolean true hr — highlight reel along the bottom
vr boolean true vr — VR button
floorSelector boolean true f — floor selector
mls boolean false mls — MLS-compliant presentation
lang string - lang — interface language
guidedTourStart number - ts — seconds before the guided tour starts
params object - Any further showcase parameter, verbatim

quickStart defaults to on because without it the space shows a black frame for several seconds while the first view resolves.

Tour thumbnails​

Matterport publishes no oEmbed endpoint and no derivable thumbnail URL, so a tour facade shows a deliberate placeholder unless you give it one:

<MediaEmbed provider="matterport" modelId="SxQL3iGyoDo" title="Show home" poster="/tour.jpg" />

fetchMetadata opts into a runtime lookup of the space's real preview image and name. It is off by default, because contacting the provider before the viewer clicks is the very thing the lazy facade avoids. The image URL Matterport returns is signed and time-limited, so it cannot be cached or precomputed.

<MediaEmbed provider="matterport" modelId="SxQL3iGyoDo" title="Show home" fetchMetadata />

The lookup never runs when poster is set, and any failure falls back to the placeholder rather than breaking the render.

Only public spaces are supported. Access-restricted models need an OAuth flow and an application key, which this component does not do.

Vimeo Posters​

YouTube thumbnails follow a predictable URL, so MediaEmbed derives them. Vimeo thumbnails require an oEmbed lookup, which would defeat the point of a facade — supply poster instead:

<MediaEmbed url="https://vimeo.com/123456789" title="Design process" poster="/vimeo-poster.jpg" />

Explicit Provider and ID​

<MediaEmbed provider="youtube" videoId="dQw4w9WgXcQ" title="A song" start={30} />

Accessibility​

  • title is required and becomes the iframe's accessible name. Without it, screen readers announce only "frame".
  • The facade is a real button, so it is keyboard reachable and activates with Enter or Space.
  • Its label names both the media and the provider, so viewers know a third-party player is about to load. A Matterport facade is labelled "Open … as a 3D tour", since it opens a space rather than playing a video.
  • The poster image is decorative and hidden from assistive technology.
On this page