> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beyondwords.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Playlist

> Render the player's content list and let users jump between items.

export const PlaylistDemo = () => {
  const MOCK_PLAYLIST = [{
    id: "7a974fb1-1b89-4be3-a25d-4ad286d5305b",
    title: "Part 1: The newsroom shift",
    imageUrl: "/images/player-ui/demo-cover.svg",
    sourceUrl: "https://beyondwords.io",
    sourceId: "demo",
    adsEnabled: true,
    duration: 124,
    audio: [{
      id: "audio-1",
      url: "/images/player-ui/demo-silent.mp3",
      contentType: "audio/mpeg",
      duration: 124
    }],
    video: [],
    summarization: {
      audio: [],
      video: []
    },
    segments: []
  }, {
    id: "129e7681-fa4d-4526-8be9-d27c946a5023",
    title: "Part 2: Voices at scale",
    imageUrl: "/images/player-ui/demo-cover.svg",
    sourceUrl: "https://beyondwords.io",
    sourceId: "demo",
    adsEnabled: true,
    duration: 98,
    audio: [{
      id: "audio-2",
      url: "/images/player-ui/demo-silent.mp3",
      contentType: "audio/mpeg",
      duration: 98
    }],
    video: [],
    summarization: {
      audio: [],
      video: []
    },
    segments: []
  }, {
    id: "e9f2257c-14cf-46c0-bab6-bcaa9e229775",
    title: "Part 3: What comes next",
    imageUrl: "/images/player-ui/demo-cover.svg",
    sourceUrl: "https://beyondwords.io",
    sourceId: "demo",
    adsEnabled: true,
    duration: 152,
    audio: [{
      id: "audio-3",
      url: "/images/player-ui/demo-silent.mp3",
      contentType: "audio/mpeg",
      duration: 152
    }],
    video: [],
    summarization: {
      audio: [],
      video: []
    },
    segments: []
  }];
  const newEvent = props => ({
    id: crypto.randomUUID(),
    createdAt: new Date().toISOString(),
    status: "pending",
    initiatedBy: "user",
    ...props
  });
  const [target, setTarget] = useState(null);
  const [player, setPlayer] = useState(null);
  const [content, setContent] = useState([]);
  const [contentIndex, setContentIndex] = useState(0);
  useEffect(() => {
    if (!target) return;
    let instance;
    const init = () => {
      instance = new window.BeyondWords.Player({
        target,
        content: MOCK_PLAYLIST,
        widgetStyle: "none",
        showUserInterface: false,
        analyticsConsent: "none"
      });
      const sync = () => {
        setContent(instance.content);
        setContentIndex(instance.contentIndex);
      };
      instance.addEventListener("<any>", sync);
      sync();
      setPlayer(instance);
    };
    if (window.BeyondWords) init(); else window.addEventListener("BeyondWordsReady", init);
    return () => {
      window.removeEventListener("BeyondWordsReady", init);
      if (instance) instance.destroy();
    };
  }, [target]);
  return <div className="not-prose flex w-full items-center justify-center rounded-lg border p-10" style={{
    minHeight: "16rem",
    fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
  }}>
      <div ref={setTarget} style={{
    display: "none"
  }} />
      {player && <ul style={{
    listStyle: "none",
    margin: 0,
    padding: 0
  }}>
          {content.map((item, index) => <li key={index}>
              <button type="button" aria-current={contentIndex === index} aria-label={`Play ${item.title}`} style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    width: "100%"
  }} onClick={() => {
    player.onEvent(newEvent({
      type: "PressedPlaylistItem",
      description: "A playlist item was pressed.",
      index
    }));
  }}>
                <span style={{
    display: "inline-flex",
    width: "1rem",
    justifyContent: "center"
  }}>
                  {contentIndex === index ? <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                      <path fillRule="evenodd" d="M5.4 8.8a1.2 1.2 0 0 1 2.4 0v6.4a1.2 1.2 0 0 1 -2.4 0ZM10.8 6a1.2 1.2 0 0 1 2.4 0v12a1.2 1.2 0 0 1 -2.4 0ZM16.2 10.4a1.2 1.2 0 0 1 2.4 0v3.2a1.2 1.2 0 0 1 -2.4 0Z" />
                    </svg> : index + 1}
                </span>
                <span>{item.title}</span>
              </button>
            </li>)}
        </ul>}
    </div>;
};

<Tabs>
  <Tab title="Preview">
    <PlaylistDemo />
  </Tab>

  <Tab title="Code">
    ```jsx Playlist.jsx theme={null}
    "use client";

    import { newEvent } from "@/lib/newEvent";
    import { NowPlayingMarkerIcon } from "@/registry/react/Icons";
    import { usePlayer, usePlayerSelector } from "@/registry/react/PlayerProvider";

    export function Playlist() {
      const player = usePlayer();
      const content = usePlayerSelector((p) => p.content);
      const contentIndex = usePlayerSelector((p) => p.contentIndex);
      return (
        <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
          {content.map((item, index) => (
            <li key={index}>
              <button
                type="button"
                aria-current={contentIndex === index}
                aria-label={`Play ${item.title}`}
                style={{ display: "flex", alignItems: "center", gap: "0.5rem", width: "100%" }}
                onClick={() => {
                  player.onEvent(
                    newEvent({
                      type: "PressedPlaylistItem",
                      description: "A playlist item was pressed.",
                      index,
                    }),
                  );
                }}
              >
                <span style={{ display: "inline-flex", width: "1rem", justifyContent: "center" }}>
                  {contentIndex === index ? <NowPlayingMarkerIcon size={16} /> : index + 1}
                </span>
                <span>{item.title}</span>
              </button>
            </li>
          ))}
        </ul>
      );
    }
    ```
  </Tab>
</Tabs>

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npx @beyondwords/cli components add playlist
  ```

  ```bash pnpm theme={null}
  pnpm dlx @beyondwords/cli components add playlist
  ```

  ```bash yarn theme={null}
  yarn dlx @beyondwords/cli components add playlist
  ```

  ```bash bun theme={null}
  bunx --bun @beyondwords/cli components add playlist
  ```
</CodeGroup>

## Usage

```tsx theme={null}
import { PlayerProvider } from '@/registry/react/PlayerProvider';
import { Playlist } from '@/registry/react/Playlist';

export function Player() {
  return (
    <PlayerProvider args={{ projectId: 9504, playlistId: 153102, showUserInterface: false }}>
      <Playlist />
    </PlayerProvider>
  );
}
```

The component maps over `content` from the surrounding
[`PlayerProvider`](/player-ui/react/player-provider). It renders a button per item and
marks the active one (`contentIndex`) with a now-playing icon. Pressing an item emits a
`PressedPlaylistItem` event (built with [`newEvent`](/player-ui/react/new-event))
carrying its `index`.

<Info>
  The event is emitted rather than setting `player.contentIndex` directly. This
  lets the player handle ads and outros correctly when switching tracks.
</Info>

## Reference

|                       |                                                                 |
| --------------------- | --------------------------------------------------------------- |
| **Props**             | None                                                            |
| **Player state read** | `content`, `contentIndex`                                       |
| **Events emitted**    | `PressedPlaylistItem` (carries the item's `index`)              |
| **Requires**          | a [`PlayerProvider`](/player-ui/react/player-provider) ancestor |
