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:
-
Query string —
theme=dark,theme=light, ortheme=systemon the embed URL.system(the default if omitted or invalid) usesprefers-color-schemeinside the iframe only (usually the visitor’s OS), not your CMS skin. -
postMessagewhen your site switches themes (parent page → iframe). Only origins allowed by the embed CSP (same host list asframe-ancestorsinnext.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× → 1.5× → 1.75× → 2× (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).