Embed and download recordings

On this page
  1. What to store
  2. Link to the video
  3. Embed the player
  4. Get download links
  5. Content Security Policy

When a recording finishes uploading, the SDK gives you a DadanVideo object. This guide shows what to store, how to play the video in your app with @dadan-io/embed, and how to get download links.

typescript
interface DadanVideo {
  id: string;
  sharedUrl: string;
  embedUrl: string;
  title?: string;
  thumbnailUrl?: string;
  duration?: number;
}

Save id and sharedUrl in your database next to the record they belong to, such as the support ticket or the job application. embedUrl can always be rebuilt from sharedUrl, and download links must be requested fresh each time (see below), so there is no need to store them.

The simplest option is a link: use sharedUrl as the link target. It opens the video’s share page on Dadan.

Install the embed package:

bash
npm install @dadan-io/embed

toEmbedUrl turns a share URL into the player URL. Use it in an iframe that you style with your own CSS:

typescript
import { toEmbedUrl } from "@dadan-io/embed";

export function createPlayer(
  sharedUrl: string,
  title: string,
): HTMLIFrameElement {
  const frame = document.createElement("iframe");
  frame.src = toEmbedUrl(sharedUrl);
  frame.title = title;
  frame.loading = "lazy";
  frame.allow = "autoplay; fullscreen; picture-in-picture";
  frame.allowFullscreen = true;
  frame.className = "video-player";
  return frame;
}
css
.video-player {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: 12px;
}

This is the best choice for pages with a strict Content Security Policy, because it needs no inline styles. Give each iframe a meaningful title, such as the video title, so screen reader users know what it contains.

buildPlayerHtml returns a responsive iframe wrapped in a container that keeps a 16:9 ratio:

typescript
import { buildPlayerHtml, toEmbedUrl } from "@dadan-io/embed";

const container = document.querySelector<HTMLDivElement>("#player")!;
container.innerHTML = buildPlayerHtml(toEmbedUrl(video.sharedUrl), 640);

The HTML is built in the browser from the embed URL, and the function refuses anything that is not an http or https URL. It uses inline style attributes for the ratio, so it needs 'unsafe-inline' in your CSP style-src. If your policy does not allow that, use the iframe approach above.

Use oEmbed for the title and thumbnail

Link to section: Use oEmbed for the title and thumbnail

oembed returns the player HTML together with metadata, which is handy for previews and cards:

typescript
import { oembed } from "@dadan-io/embed";

const result = await oembed(video.sharedUrl, { width: 640 });
console.log(
  result.title,
  result.thumbnail_url,
  result.width,
  result.height,
);
document.querySelector("#player")!.innerHTML = result.html;

oembed asks Dadan’s oEmbed endpoint for the title, size and thumbnail, then builds the player HTML locally. HTML returned by the server is never inserted. Pass { fallback: true } to skip the request and only build the player.

If your app shows user content that may contain Dadan links, these helpers replace the links with players:

  • linkReplace(selector) replaces matching elements on the page whose link (or text) is a Dadan share URL, and returns how many were replaced.
  • textReplace(text) returns HTML in which every Dadan share URL in a plain text string is replaced by a player. The rest of the text is escaped.
typescript
import { linkReplace } from "@dadan-io/embed";

const replaced = await linkReplace(".comment-body a");
console.log(`${replaced} Dadan videos embedded`);

@dadan-io/record-sdk-react includes a component for this:

tsx
import { DadanEmbed } from "@dadan-io/record-sdk-react";

export function TicketVideo({ sharedUrl }: { sharedUrl: string }) {
  return <DadanEmbed sharedUrl={sharedUrl} width={720} />;
}

Download links let your users save the original file, or let your app pass it to another system. They are requested with your public app ID from an allowlisted origin, with no upload token or API key:

typescript
import {
  fetchVideoDownloadWithRetry,
  VideoDownloadError,
} from "@dadan-io/record-sdk";

async function downloadLinks(videoId: string) {
  try {
    const links = await fetchVideoDownloadWithRetry({
      videoId,
      publicAppId: import.meta.env.VITE_DADAN_PUBLIC_APP_ID,
    });
    return links.videoRenditions ?? [];
  } catch (error) {
    if (
      error instanceof VideoDownloadError &&
      error.code === "VIDEO_NOT_FOUND"
    )
      return [];
    throw error;
  }
}

The response contains:

FieldContent
videoRenditionsA list of { height, label, url }, one per available quality. The first entry is the original
audioLinkAn audio-only file, when available
captionUrlA captions file, when available

Important points:

  • Links expire quickly. The file URLs are signed and short-lived. Never store or cache them; request new ones when the user clicks Download.
  • New videos need processing time. Right after an upload, the API can answer VIDEO_STILL_PROCESSING. fetchVideoDownloadWithRetry retries with a growing delay (up to 6 attempts by default). Use fetchVideoDownload for a single attempt.
  • Only your app’s videos. Downloads work for videos recorded through the same public app ID.
  • Treat video IDs with care. Any page on your allowlisted origins that knows a video ID can request its download links. Do not expose the IDs of private recordings to people who should not download them.

For the original file only, getOriginalDownloadUrl({ videoId, publicAppId }) returns its URL or null.

If you use DadanRecorder, the same calls are methods on the recorder: await recorder.fetchDownload() returns the links for the last recording and await recorder.openDownload() opens the original file in a new tab.

CodeMeaning
INVALID_APPThe public app ID is not valid
ORIGIN_DENIEDThe page’s origin is not on the app’s allowlist
PLAN_DENIEDThe Dadan plan behind the app does not include this download
VIDEO_NOT_FOUNDNo video with this ID for this app
VIDEO_STILL_PROCESSINGThe video is still being processed. Retry later
VIDEO_TRANSCODE_FAILEDProcessing failed for this video
UNKNOWNAny other failure

Every VideoDownloadError also carries httpStatus. Its detail field holds the raw server text for your logs; do not show it to users.

If your site sends a CSP, the player and the API need these sources:

text
frame-src   https://app.dadan.io
connect-src https://app.dadan.io

frame-src allows the player iframe. connect-src covers oEmbed metadata and download links. The full policy for recording is in the Record SDK production checklist.

Ask AI about this article

What should the AI help you with?

Are you still facing issues?

Reach out to our support team for further assistance.

dadan