Skip to content

[Feature] Persistent mini-player status bar #150

Description

@lqdev

Summary

Add a persistent 1-2 line "now playing" status bar at the bottom of every buffer, showing the current episode, playback state, position, and volume — regardless of which buffer is active.

Related: #131 (Meta-Epic: Cross-Platform Audio Playback)

Problem / Background

The NowPlaying buffer (#140) shows playback info, but only when the user switches to it. While browsing podcasts, episodes, or playlists, there's no indication of what's currently playing. Users must switch buffers (F6) just to check playback state.

Popular terminal music players (cmus, ncmpcpp) solve this with a persistent status bar.

Proposed Solution

1. Layout Change

Reserve 1-2 lines at the bottom of the terminal for the mini-player, above the minibuffer/status line:

┌─ Podcasts ─────────────────────────────────────────────┐
│ > My Podcast                                            │
│   Another Podcast                                       │
│   ...                                                   │
│                                                         │
├─────────────────────────────────────────────────────────┤
│ ▶ Episode Title - Podcast Name  12:34/45:00  Vol: 80%  │
├─────────────────────────────────────────────────────────┤
│ :command                                    [Podcasts]  │
└─────────────────────────────────────────────────────────┘

2. Implementation

In the main render function (src/ui/app.rs, render() method):

fn render(&self, frame: &mut Frame) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Min(1),        // Main buffer area
            Constraint::Length(1),      // Mini-player bar (NEW)
            Constraint::Length(1),      // Status/minibuffer line
        ])
        .split(frame.area());

    // Render active buffer
    self.render_buffer(frame, chunks[0]);

    // Render mini-player (always visible when playing)
    self.render_mini_player(frame, chunks[1]);

    // Render status line / minibuffer
    self.render_status(frame, chunks[2]);
}

3. Mini-Player Widget

fn render_mini_player(&self, frame: &mut Frame, area: Rect) {
    if let Some(ref rx) = self.playback_status_rx {
        let status = rx.borrow();
        match status.state {
            PlaybackState::Playing | PlaybackState::Paused => {
                let icon = if status.state == PlaybackState::Playing { "▶" } else { "⏸" };
                let pos = format_duration(status.position);
                let dur = format_duration(status.duration);
                let vol = format!("Vol: {}%", (status.volume * 100.0) as u32);
                let text = format!("{icon} {title}  {pos}/{dur}  {vol}");
                // Render with theme styles
            }
            PlaybackState::Stopped => {
                // Hide mini-player or show empty line
            }
        }
    }
}

4. Adaptive Layout

  • When nothing is playing: hide the mini-player line (give space back to buffer)
  • When playing: show the 1-line bar
  • Truncate episode title if terminal is narrow
  • Right-align position and volume

Files to Modify

File Change
src/ui/app.rs Modify render layout to include mini-player row
src/ui/components/mini_player.rs New — Mini-player widget component
src/ui/themes.rs Add mini-player styles (background, text colors)

Acceptance Criteria

  • Mini-player bar visible at bottom when audio is playing
  • Shows: state icon, episode title, position/duration, volume
  • Hidden when nothing is playing (no wasted space)
  • Works in every buffer (podcast list, episode list, detail, help, etc.)
  • Adapts to terminal width (truncates title if needed)
  • Uses Theme styles (no hardcoded colors)
  • Does not interfere with minibuffer/status line

Implementation Notes

  • This changes the global render layout — needs careful testing with all buffers
  • The mini-player is NOT a buffer (not managed by BufferManager) — it's a persistent UI component rendered by UIApp::render() directly
  • Consider making it optional via config: config.ui.show_mini_player: bool
  • The NowPlaying buffer ([Feature] NowPlaying buffer #140) still exists for detailed view — mini-player is the quick glance

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priorityaudioAudio playback componentenhancementNew feature or requestuiUI / TUI component

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions