AskElephant Audio
← All docs

Embedding audio in the Knowledge Base

The library serves two patterns: hosted iframe (preferred) and raw Plyr when your CMS strips iframes.

Iframe (hosted player)

Use the embed route so playback, speed controls, and analytics stay consistent. Replace your-slug with the lesson slug from the library.

<iframe
  src="https://learn.askelephant.ai/embed/your-slug?theme=system"
  width="100%"
  height="110"
  frameborder="0"
  title="AskElephant audio resource"
></iframe>

Dark mode: The iframe document is isolated from your KB page, so it does not automatically follow your site’s theme toggle. You can:

  1. Query stringtheme=dark, theme=light, or theme=system on the embed URL. system (the default if omitted or invalid) uses prefers-color-scheme inside the iframe only (usually the visitor’s OS), not your CMS skin.

  2. postMessage when your site switches themes (parent page → iframe). Only origins allowed by the embed CSP (same host list as frame-ancestors in next.config.ts) are accepted:

const embedOrigin = "https://learn.askelephant.ai";
iframe.contentWindow?.postMessage(
  { source: "ask-elephant-embed-host", type: "set-theme", theme: "dark" },
  embedOrigin
);

Use "light", "dark", or "system" for theme. Call this whenever your KB toggles appearance so the player stays in sync.

Speed: The hosted embed and the full lesson player use the same mono pill after the seek bar (with a light vertical separator). Each tap cycles playback rate 0.5×1.5×1.75× (no dropdown).

CSP: The embed response sets Content-Security-Policy: frame-ancestors in next.config.ts (e.g. askelephant.ai, *.askelephant.ai, ask-elephant.support.site, *.support.site, and local dev). If your KB lives on another domain, add that exact https:// origin (or a matching wildcard where CSP allows it) to frameAncestors and redeploy.

R2 CORS: The MP3 must allow your KB origin (and learn.askelephant.ai) for the browser to fetch audio inside the iframe.

Troubleshooting “refused to connect” in the iframe: If the embed src is a Vercel preview URL (*.vercel.app, *.askelephant.dev, etc.), Vercel Deployment Protection can block the document load inside a third-party iframe (no login cookie). Use a production deployment URL with protection off for the embed, or adjust protection / bypass rules for that project. In DevTools → Network, check the request to /embed/... (status 401/302 to login often means protection). A CSP frame-ancestors mismatch usually shows a different console error naming frame-ancestors.

Raw Plyr (no iframe)

Point Plyr at the same HTTPS URL stored in Postgres (audio_url). Replace YOUR_MP3_URL and pin versions in production if you rely on this long term.

<link rel="stylesheet" href="https://cdn.plyr.io/3.8.4/plyr.css" />
<audio id="ae-audio" controls playsinline></audio>
<script src="https://cdn.plyr.io/3.8.4/plyr.js"></script>
<script>
  const player = new Plyr("#ae-audio", {
    controls: [
      "play",
      "progress",
      "current-time",
      "duration",
      "mute",
      "volume",
      "settings",
    ],
    settings: ["speed"],
    speed: { selected: 1, options: [0.5, 1, 1.5, 1.75, 2] },
  });
  player.source = {
    type: "audio",
    sources: [{ src: "YOUR_MP3_URL", type: "audio/mp3" }],
  };
</script>

PostHog source values

Playback analytics use source: embed (iframe), direct (opened lesson URL without ?ref=library), or library (opened from the library with ?ref=library).