Skip to content

Embed the player

Every video comes with a hosted player page you embed with an iframe; there is no JavaScript SDK to install. The player handles adaptive quality, captions, chapters, scrub previews, and analytics out of the box.

Embedding a video

The video's link field in the API ⧉ holds the embed URL. For a public video it is a permanent URL of the form /e/<project_id>/<video_id>/:

<iframe src="https://staging.heapstream.com/e/<project_id>/<video_id>/"
        width="640" height="360"
        allowFullScreen
        style="border: none;"
        allow="fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>

The dashboard's Embed dialog on each video generates this snippet for you.

Private videos need a signed token: generate a JWT and use the /et/<project_id>/?token=<jwt> URL instead; see Secure video playback. Extra parameters like autoplay and player_id carry through.

Password-protected videos (a private video with a password set) are embedded by appending ?password=<password> to the URL. The dashboard's embed dialog injects it automatically. A video's password field can hold several comma-separated passwords, all of which work; a wrong password renders a blocked page instead of the player.

URL parameters

Parameter Values Effect
autoplay any non-empty value Start playback as soon as the player loads. The player mutes itself to satisfy browser autoplay policies.
t seconds (integer) Start playback at this time offset.
volume 0–1 Initial volume.
player_id a player id Render with a specific one of the project's players instead of the default. An unknown id falls back to the default player.
password string Unlocks a password-protected video (see above).
token JWT Access token for private videos (only on /et/… and the signed asset URLs).
uid string Sets the viewer id used by analytics to count unique viewers; pass your own user id to track logged-in viewers across devices.

There are no muted or loop parameters; muting happens automatically with autoplay, and looping is a per-player setting: switch on Show Loop and viewers get a Loop toggle in the player's settings (cog) menu that repeats the video until they turn it off.

Responsive sizing

The iframe itself is fixed-size (the player fills whatever box it's given). For a fluid 16:9 embed, size the iframe with CSS instead of the width/height attributes:

<iframe src="…" allowFullScreen
        style="border: none; width: 100%; aspect-ratio: 16 / 9;"
        allow="fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>

Customizing the player

Players are per-project profiles: each project has a default player, and you can create more and select one per embed with ?player_id=. Configure them on the dashboard's Player Edit page or through the player API ⧉:

  • Controls: toggle the quality, speed, zoom, and share menus, the viewer Loop toggle, the ±10 s rewind buttons, the title overlay, and the scrub-bar storyboard previews.
  • Logos: three independent PNG uploads, an in-player corner logo (with a corner picker: top_left, top_right, bottom_left, bottom_right), a control-bar logo, and a title logo. Images are resized to fit automatically.
  • Behavior: resume-from-last-position and pause-when-tab-hidden are configured per player on the Player Edit page.

Keyboard shortcuts

Key Action
Space or K Play / pause
M Mute / unmute
F Fullscreen
C Captions on / off
I Picture-in-picture
Left / Right Seek −5 s / +5 s
J / L Seek −10 s / +10 s
Up / Down Volume up / down
> / < Playback speed up / down
0 – 9 Jump to 0–90% of the video
Home / End Jump to start / end

Resume from last position

With the player's resume setting enabled, the player remembers each viewer's position (in the browser's local storage) and picks up where they left off. Positions in the first two seconds count as "not started", finishing a video clears the stored position, and a viewer who stopped within the last second starts from the beginning instead of resuming into the credits.

Restricting where the player embeds

By default the embed page can be framed by any site. To restrict it, set the project's allowed domains: embeds on other sites are then blocked (the blocked page offers a "Watch the video" link to the video's permalink, when one is set).

While a video is processing

Embedding a video that is still encoding shows a "Video is being encoded" placeholder; the player appears automatically once the essential renditions are ready.

SEO and social sharing

Every embed page describes its video to search engines and to the apps that expand a pasted link. You don't configure any of it.

What search engines read

The page carries VideoObject ⧉ structured data: the title, description, poster, upload date and duration, a link to the player, and, when the video has chapters, each one as a clip with its own start time and deep link. It also tells Google how to jump to any second of the video, which is what lets a result show key moments you can click straight into.

The embed page asks not to be indexed on its own, but to count when it is embedded (noindex, indexifembedded). That combination is deliberate: a bare player page competing in search results with the page you put it on helps nobody. Instead the markup is attributed to your page, so the video can appear in search alongside the article or product it belongs to, without you writing any markup yourself.

Only Google understands the "count when embedded" half. Other search engines read the same directive, don't recognize that part, and simply don't index the embed page at all, which is the outcome we want from them anyway.

Paste an embed link into Slack, X, Facebook, iMessage or Discord and it expands into a card with the video's poster, title and description, rather than a bare URL. This uses Open Graph and Twitter card tags on the same page.

Private videos stay private

The tags are only rendered when the request is actually allowed to see the video. Someone who pastes a link to a private video into a channel where others can't open it produces a plain link, not a card: the people who can't watch it don't learn its title or see its poster either.

Links carrying a password or an owner-preview token stay out of the markup too. The URLs written into the page are always the plain, canonical embed URL, so no secret that unlocked your view is published in the page or passed on to a search engine.