diff --git a/apps/docs/content/components/(voice)/audio-player.mdx b/apps/docs/content/components/(voice)/audio-player.mdx
index 1a56648b..4b5f8a16 100644
--- a/apps/docs/content/components/(voice)/audio-player.mdx
+++ b/apps/docs/content/components/(voice)/audio-player.mdx
@@ -18,6 +18,7 @@ The `AudioPlayer` component provides a flexible and customizable audio playback
- Fully composable architecture with granular control components
- ButtonGroup integration for cohesive control layout
- Individual control components (play, seek, volume, etc.)
+- Playback speed control with configurable rates
- Flexible layout with customizable control bars
- CSS custom properties for deep theming
- Shadcn/ui Button component styling
@@ -150,6 +151,25 @@ Seek forward button wrapped in a shadcn Button component.
}}
/>
+### ``
+
+Playback speed button, wrapped in a shadcn Button component.
+
+",
+ default: "[0.5, 1, 1.2, 1.5, 1.7, 2]",
+ },
+ "...props": {
+ description:
+ "Any other props are spread to the MediaPlaybackRateButton component.",
+ type: 'Omit, "rates">',
+ },
+ }}
+/>
+
### ``
Displays the current playback time, wrapped in ButtonGroupText.
diff --git a/packages/elements/__tests__/audio-player.test.tsx b/packages/elements/__tests__/audio-player.test.tsx
index fbbf8ff6..dee0c0ab 100644
--- a/packages/elements/__tests__/audio-player.test.tsx
+++ b/packages/elements/__tests__/audio-player.test.tsx
@@ -7,6 +7,7 @@ import {
AudioPlayerElement,
AudioPlayerMuteButton,
AudioPlayerPlayButton,
+ AudioPlayerPlaybackRateButton,
AudioPlayerSeekBackwardButton,
AudioPlayerSeekForwardButton,
AudioPlayerTimeDisplay,
@@ -317,6 +318,60 @@ describe("audioPlayerMuteButton", () => {
});
});
+describe("audioPlayerPlaybackRateButton", () => {
+ it("renders playback rate button", () => {
+ const { container } = render();
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toBeInTheDocument();
+ });
+
+ it("uses default playback rates", () => {
+ const { container } = render();
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toHaveAttribute("rates", "0.5 1 1.2 1.5 1.7 2");
+ });
+
+ it("accepts custom playback rates", () => {
+ const { container } = render(
+
+ );
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toHaveAttribute("rates", "0.75 1 1.25");
+ });
+
+ it("applies custom className", () => {
+ const { container } = render(
+
+ );
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toHaveClass("custom-rate");
+ });
+
+ it("uses a wider text-friendly button size", () => {
+ const { container } = render();
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toHaveClass("w-14");
+ });
+
+ it("prevents selecting playback rate text", () => {
+ const { container } = render();
+ const button = container.querySelector(
+ '[data-slot="audio-player-playback-rate-button"]'
+ );
+ expect(button).toHaveClass("select-none");
+ });
+});
+
describe("audioPlayerVolumeRange", () => {
it("renders volume range slider", () => {
const { container } = render();
@@ -348,6 +403,7 @@ const renderCompleteAudioPlayer = () =>
+
@@ -408,6 +464,13 @@ describe("integration tests", () => {
).toBeInTheDocument();
});
+ it("renders playback rate button", () => {
+ const { container } = renderCompleteAudioPlayer();
+ expect(
+ container.querySelector('[data-slot="audio-player-playback-rate-button"]')
+ ).toBeInTheDocument();
+ });
+
it("handles AI SDK speech result data format", () => {
const mockSpeechData = {
base64: "dGVzdA==",
diff --git a/packages/elements/src/audio-player.tsx b/packages/elements/src/audio-player.tsx
index a340e76b..c860dded 100644
--- a/packages/elements/src/audio-player.tsx
+++ b/packages/elements/src/audio-player.tsx
@@ -13,6 +13,7 @@ import {
MediaDurationDisplay,
MediaMuteButton,
MediaPlayButton,
+ MediaPlaybackRateButton,
MediaSeekBackwardButton,
MediaSeekForwardButton,
MediaTimeDisplay,
@@ -198,6 +199,30 @@ export const AudioPlayerDurationDisplay = ({
);
+export type AudioPlayerPlaybackRateButtonProps = Omit<
+ ComponentProps,
+ "rates"
+> & {
+ rates?: ArrayLike;
+};
+
+const DEFAULT_PLAYBACK_RATES = [0.5, 1, 1.2, 1.5, 1.7, 2];
+
+export const AudioPlayerPlaybackRateButton = ({
+ className,
+ rates = DEFAULT_PLAYBACK_RATES,
+ ...props
+}: AudioPlayerPlaybackRateButtonProps) => (
+
+);
+
export type AudioPlayerMuteButtonProps = ComponentProps;
export const AudioPlayerMuteButton = ({
diff --git a/packages/examples/src/audio-player-remote.tsx b/packages/examples/src/audio-player-remote.tsx
index 4047356c..bf2bdd7e 100644
--- a/packages/examples/src/audio-player-remote.tsx
+++ b/packages/examples/src/audio-player-remote.tsx
@@ -7,6 +7,7 @@ import {
AudioPlayerElement,
AudioPlayerMuteButton,
AudioPlayerPlayButton,
+ AudioPlayerPlaybackRateButton,
AudioPlayerSeekBackwardButton,
AudioPlayerSeekForwardButton,
AudioPlayerTimeDisplay,
@@ -25,6 +26,7 @@ const Example = () => (
+
diff --git a/packages/examples/src/audio-player.tsx b/packages/examples/src/audio-player.tsx
index a97ade58..a0c91514 100644
--- a/packages/examples/src/audio-player.tsx
+++ b/packages/examples/src/audio-player.tsx
@@ -7,6 +7,7 @@ import {
AudioPlayerElement,
AudioPlayerMuteButton,
AudioPlayerPlayButton,
+ AudioPlayerPlaybackRateButton,
AudioPlayerSeekBackwardButton,
AudioPlayerSeekForwardButton,
AudioPlayerTimeDisplay,
@@ -57,6 +58,7 @@ const Example = () => {
+
diff --git a/skills/ai-elements/references/audio-player.md b/skills/ai-elements/references/audio-player.md
index 2bd7b944..936f47f5 100644
--- a/skills/ai-elements/references/audio-player.md
+++ b/skills/ai-elements/references/audio-player.md
@@ -18,6 +18,7 @@ npx ai-elements@latest add audio-player
- Fully composable architecture with granular control components
- ButtonGroup integration for cohesive control layout
- Individual control components (play, seek, volume, etc.)
+- Playback speed control with configurable rates
- Flexible layout with customizable control bars
- CSS custom properties for deep theming
- Shadcn/ui Button component styling
@@ -93,6 +94,15 @@ Seek forward button wrapped in a shadcn Button component.
| `seekOffset` | `number` | `10` | The number of seconds to seek forward. |
| `...props` | `React.ComponentProps` | - | Any other props are spread to the MediaSeekForwardButton component. |
+### ``
+
+Playback speed button, wrapped in a shadcn Button component.
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `rates` | `ArrayLike` | `[0.5, 1, 1.2, 1.5, 1.7, 2]` | Playback rates to cycle through. |
+| `...props` | `Omit, "rates">` | - | Any other props are spread to the MediaPlaybackRateButton component. |
+
### ``
Displays the current playback time, wrapped in ButtonGroupText.
diff --git a/skills/ai-elements/scripts/audio-player-remote.tsx b/skills/ai-elements/scripts/audio-player-remote.tsx
index 19d8d89d..ba1e8c5a 100644
--- a/skills/ai-elements/scripts/audio-player-remote.tsx
+++ b/skills/ai-elements/scripts/audio-player-remote.tsx
@@ -7,6 +7,7 @@ import {
AudioPlayerElement,
AudioPlayerMuteButton,
AudioPlayerPlayButton,
+ AudioPlayerPlaybackRateButton,
AudioPlayerSeekBackwardButton,
AudioPlayerSeekForwardButton,
AudioPlayerTimeDisplay,
@@ -25,6 +26,7 @@ const Example = () => (
+
diff --git a/skills/ai-elements/scripts/audio-player.tsx b/skills/ai-elements/scripts/audio-player.tsx
index bdfd8588..40a0dcc3 100644
--- a/skills/ai-elements/scripts/audio-player.tsx
+++ b/skills/ai-elements/scripts/audio-player.tsx
@@ -7,6 +7,7 @@ import {
AudioPlayerElement,
AudioPlayerMuteButton,
AudioPlayerPlayButton,
+ AudioPlayerPlaybackRateButton,
AudioPlayerSeekBackwardButton,
AudioPlayerSeekForwardButton,
AudioPlayerTimeDisplay,
@@ -57,6 +58,7 @@ const Example = () => {
+