Elements

Media player

Audio and video a tool returned, played inline without leaving the thread.

C major arpeggio
0:00 / 0:05
fig. 01

Installation

npx shadcn@latest add "@assistant-ui/elements-media-player"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Media player keeps a response's recording or clip in the conversation, with explicit controls and no autoplay.

Getting started

File turns playable audio/* and video/* file parts into the matching player automatically. Keep them as file parts so the runtime preserves the same wire shape for every adapter.

Render media file parts

components/assistant-ui/elements/thread.aui.tsx
import { File } from "@/components/assistant-ui/elements/file";
import { MessagePrimitive } from "@assistant-ui/react";

function AssistantMessage() {
  return (
    <MessagePrimitive.Root>
      <MessagePrimitive.Parts components={{ File }} />
    </MessagePrimitive.Root>
  );
}

An audio/mpeg or video/mp4 file with an https:, blob:, data: or base64 payload now plays in place and keeps its download action.

Return an audio URL from a backend tool

app/tools/briefing.ts
export const createBriefingTool = {
  execute: async () => ({
    audioUrl: await createBriefingAudio(),
  }),
};

Send the URL back as a file part when the assistant reports the result:

{
  type: "file",
  filename: "briefing.mp3",
  mimeType: "audio/mpeg",
  data: audioUrl,
  sourceType: "url",
}

Anatomy

<div data-slot="audio-player">
  <button aria-label="Play audio" />
  <input type="range" aria-label="Seek" />
  <audio preload="metadata" />
</div>

<div data-slot="video-player">
  <video controls playsInline preload="metadata" />
</div>

Audio uses the metadata duration when the browser has it, then falls back to durationMs. Its seek control reports the current position and total duration to assistive technology. Video leaves playback controls to the browser and shows a title and duration below the frame when either is known.

Examples

Audio artwork

artwork adds a 40px cover image without changing the playback controls:

<AudioPlayer
  src={episode.url}
  title={episode.title}
  artwork={episode.coverUrl}
  durationMs={episode.durationMs}
/>

Video framing

ratio defaults to "16:9". Choose "4:3", "1:1", or "9:16" for a fixed frame, or "auto" when the video should size itself:

<VideoPlayer src={clip.url} title={clip.title} ratio="9:16" />

API reference

AudioPlayer

PropTypeDefaultDescription
srcstringrequiredAudio URL or data URI.
titlestringLabel for the player and play button.
artworkstringOptional 40px cover image URL.
durationMsnumberFallback duration until metadata loads.

VideoPlayer

PropTypeDefaultDescription
srcstringrequiredVideo URL or data URI.
posterstringPoster image URL for the native video element.
titlestringCaption and accessible video label.
ratio"16:9" | "4:3" | "1:1" | "9:16" | "auto""16:9"Frame aspect ratio.
durationMsnumberFallback duration until metadata loads.