# Music player

> A music player UI in Python: library sidebar, album header with gradient art, a track list with an animated equalizer and a player bar with live progress and volume.

Source: https://albakore.github.io/pyzed/blocks/music-player/

A media player: a library sidebar with playlists, an album header with gradient cover art and a soft shadow, a track list where the playing song shows an animated equalizer, and a player bar with shuffle, previous, play / pause, next, repeat, a seek bar and volume. The timer is a real subscription, so the progress moves while it plays.

The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark.

## Run it

From the repository root:

```bash
python examples/blocks/music_player.py
```

## What it shows

- `pz.every` drives the playback clock only while playing (`subscriptions()`).
- Cover art from `pz.Gradient`, a header that fades out with `pz.Paint(..., mask=pz.Mask([1, 0]))`.
- An equalizer made of four small containers whose heights follow the clock.
- Rows are `pz.button`s with hover and selected backgrounds from the theme.

## Components used

| Component | Used for |
| --- | --- |
| [Slider](https://albakore.github.io/pyzed/library/slider/) | Seek bar and volume |
| [Icon button](https://albakore.github.io/pyzed/library/icon-button/) | Player controls |
| [List item](https://albakore.github.io/pyzed/library/list-item/) | Library and playlists |
| [List header](https://albakore.github.io/pyzed/library/list-header/) | Playlists title |
| [Button](https://albakore.github.io/pyzed/library/button/) | Play / pause |
| [Scrollable](https://albakore.github.io/pyzed/library/scrollable/) | Album page |
| [Effects](https://albakore.github.io/pyzed/library/effects/) | Gradients and shadows |

## Code

The whole block, ready to copy. It only needs `pip install pyzed-ui`.

```python title="examples/blocks/music_player.py"
"""Music player block: a library sidebar, an album page with a track list and a player bar with live progress."""

import math

import pyzed as pz
from pyzed import ui

COVER = pz.Gradient([(0.0, "#f97316"), (0.5, "#db2777"), (1.0, "#4f46e5")], angle=135)
PLAYLISTS = [("Synthwave nights", "#8b5cf6"), ("Deep focus", "#14b8a6"), ("Morning run", "#f59e0b"),
             ("Lo-fi beats", "#ec4899"), ("Jazz classics", "#3b82f6")]
TRACKS = [
    ("Neon Skyline", "The Midnight Arcade", 214),
    ("Afterglow", "The Midnight Arcade", 187),
    ("Chrome Hearts", "The Midnight Arcade feat. Vela", 242),
    ("Pixel Rain", "The Midnight Arcade", 201),
    ("Overdrive", "The Midnight Arcade", 229),
    ("Last Arcade", "The Midnight Arcade", 263),
    ("Slow Fade", "The Midnight Arcade", 198),
]

def clock(seconds):
    return f"{int(seconds) // 60}:{int(seconds) % 60:02d}"

class MusicPlayer(pz.App):
    title = "Music"
    size = (1200, 760)

    def __init__(self):
        self.current = 2
        self.elapsed = 96.0
        self.playing = True
        self.volume = 0.7
        self.shuffle, self.repeat = False, True
        self.liked = {0, 2, 4}

    def subscriptions(self):
        return [pz.every(0.25, self.tick, key="clock")] if self.playing else []

    # -- actions ---------------------------------------------------------------------------
    def tick(self):
        self.elapsed += 0.25
        if self.elapsed >= TRACKS[self.current][2]:
            self.skip(1)

    def play(self, index):
        self.current, self.elapsed, self.playing = index, 0.0, True

    def skip(self, step):
        self.play((self.current + step) % len(TRACKS))

    def toggle_like(self, index):
        self.liked ^= {index}

    # -- pieces ----------------------------------------------------------------------------
    def sidebar(self):
        lists = [ui.list_item(name, None, height=34, start=pz.container(width=22, height=22).style(
                     background=pz.Gradient([color, "#111827"], angle=135), radius=5))
                 for name, color in PLAYLISTS]
        return pz.column([
            pz.column([ui.list_item("Home", None, icon="house", height=32),
                       ui.list_item("Search", None, icon="search", height=32),
                       ui.list_item("Library", None, icon="library", selected=True, height=32)], spacing=2),
            ui.list_header("Playlists", end=ui.icon_button("plus", None, size="sm")),
            pz.column(lists, spacing=2),
        ], spacing=12, width=230, height="fill", padding=(16, 10)).style(background="panel")

    def equalizer(self):
        """Animated bars next to the playing track (they move with the position)."""
        bars = []
        for i in range(4):
            level = 0.35 + 0.65 * abs(math.sin(self.elapsed * 3.1 + i * 1.7)) if self.playing else 0.3
            bars.append(pz.container(width=3, height=4 + 10 * level).style(background="accent", radius=1))
        return pz.container(pz.row(bars, spacing=2, align="end"), width=24, height=16, align_y="end")

    def header(self):
        return pz.row([
            pz.container(pz.icon("disc-3", size=72, color="#ffffffcc"), width=190, height=190, center=True).style(
                background=COVER, radius=14, shadow=("#00000066", (0, 12), 30)),
            pz.column([
                ui.label("ALBUM", size="xs", weight="semibold", color="text_muted"),
                pz.text("Midnight Arcade", size=44, weight="bold"),
                ui.muted("The Midnight Arcade · 2026 · 7 songs, 25 min"),
                pz.space(height=8),
                pz.row([
                    ui.button("Play" if not self.playing else "Pause", lambda: setattr(self, "playing", not self.playing),
                              icon="pause" if self.playing else "play", variant="solid", size="lg"),
                    ui.icon_button("shuffle", lambda: setattr(self, "shuffle", not self.shuffle),
                                   selected=self.shuffle, tooltip="Shuffle"),
                    ui.icon_button("heart", None, tooltip="Save to library"),
                    ui.icon_button("ellipsis", None),
                ], spacing=8, align="center"),
            ], spacing=6),
        ], spacing=28, align="end")

    def tracks(self):
        rows = []
        for i, (title, artist, length) in enumerate(TRACKS):
            playing = i == self.current
            number = self.equalizer() if playing else ui.muted(str(i + 1))
            rows.append(pz.button(
                pz.row([
                    pz.container(number, width=28),
                    pz.column([ui.label(title, weight="medium", color="accent" if playing else "text"),
                               ui.muted(artist, size="sm")], spacing=1, width="fill"),
                    ui.icon_button("heart", lambda i=i: self.toggle_like(i), size="sm",
                                   color=self.resolved_theme().accent if i in self.liked else None),
                    pz.container(ui.muted(clock(length)), width=50, align_x="end"),
                ], spacing=12, align="center"),
                lambda i=i: self.play(i), width="fill", padding=(6, 12), radius=8,
                bg=self.resolved_theme().ghost_selected if playing else "#00000000",
                hover_bg=self.resolved_theme().ghost_hover))
        return pz.column(rows, spacing=2)

    def player(self):
        title, artist, length = TRACKS[self.current]
        now = pz.row([
            pz.container(pz.icon("disc-3", size=22, color="#ffffffcc"), width=48, height=48, center=True).style(
                background=COVER, radius=8),
            pz.column([ui.label(title, weight="semibold"), ui.muted(artist, size="sm")], spacing=2),
            ui.icon_button("heart", lambda: self.toggle_like(self.current), size="sm",
                           color=self.resolved_theme().accent if self.current in self.liked else None),
        ], spacing=12, align="center", width=300)
        controls = pz.column([
            pz.row([
                ui.icon_button("shuffle", lambda: setattr(self, "shuffle", not self.shuffle), selected=self.shuffle),
                ui.icon_button("skip-back", lambda: self.skip(-1)),
                pz.button(pz.icon("pause" if self.playing else "play", size=18, color="#111111"),
                          lambda: setattr(self, "playing", not self.playing), width=40, height=40, padding=11,
                          radius=20, bg="#ffffff", hover_bg="#e5e5e5"),
                ui.icon_button("skip-forward", lambda: self.skip(1)),
                ui.icon_button("repeat", lambda: setattr(self, "repeat", not self.repeat), selected=self.repeat),
            ], spacing=14, align="center"),
            pz.row([
                ui.muted(clock(self.elapsed), size="xs"),
                ui.slider(self.elapsed, lambda v: setattr(self, "position", v), min=0, max=length, step=1,
                          width=340),
                ui.muted(clock(length), size="xs"),
            ], spacing=10, align="center"),
        ], spacing=6, align="center", width="fill")
        volume = pz.row([
            ui.icon_button("list-music", None, size="sm"),
            ui.icon_button("volume-2" if self.volume else "volume-x",
                           lambda: setattr(self, "volume", 0 if self.volume else 0.7), size="sm"),
            ui.slider(self.volume, lambda v: setattr(self, "volume", v), width=110),
        ], spacing=6, align="center", width=300)
        return pz.container(pz.row([now, controls, pz.row([pz.hspace(), volume])], align="center"),
                            padding=(12, 20), width="fill").style(background="title_bar")

    def view(self):
        main = pz.scrollable(pz.column([
            pz.container(self.header(), padding=32, width="fill").style(
                background=pz.Paint(COVER, opacity=0.28, mask=pz.Mask([1, 0]))),
            pz.container(self.tracks(), padding=(0, 24, 24, 24)),
        ], spacing=8), height="fill")
        return pz.column([
            pz.row([self.sidebar(), ui.divider(vertical=True), pz.container(main, width="fill", height="fill").style(
                background="editor")], height="fill"),
            ui.divider(),
            self.player(),
        ], height="fill")

if __name__ == "__main__":
    pz.run(MusicPlayer)
```

## Make it yours

Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/music_player")` and adapt it for you.
