feat(mini-player): floating mini window — early alpha (#162)

A second webview window (label "mini") with a compact player: album art,
title, artist, prev/play/next, progress bar, pin-on-top toggle, expand
back to main, close. Main minimizes on open and restores when the mini
is hidden or closed. Spacebar toggles, arrow keys skip tracks.

Cross-window sync: main window subscribes to playerStore and pushes
`mini:sync` via emitTo on track / play-state change. Audio progress
already broadcasts to all windows via `audio:progress`. Control actions
(prev/next/toggle) come back via `mini:control`; main-window restore
goes through a direct Rust command because WebKitGTK pauses JS in a
minimized webview.

Tiling-WM detection reused from existing `is_tiling_wm()` — always-on-top
is skipped on Hyprland/Sway/i3 since it's ignored there anyway.

Early alpha: no queue expand, no lyrics, no EQ, no drag-snap. Scope kept
deliberately tight for v1; follow-ups planned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-18 23:32:20 +02:00
parent 72e193cf2c
commit cef2db92cb
8 changed files with 560 additions and 2 deletions
+13 -1
View File
@@ -2,8 +2,10 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from '
import { createPortal } from 'react-dom';
import {
Play, Pause, SkipBack, SkipForward, Volume2, VolumeX, Music,
Square, Repeat, Repeat1, Maximize2, SlidersVertical, X, Heart, Cast
Square, Repeat, Repeat1, Maximize2, SlidersVertical, X, Heart, Cast,
PictureInPicture2,
} from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { usePlayerStore } from '../store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { useAuthStore } from '../store/authStore';
@@ -310,6 +312,16 @@ export default function PlayerBar() {
<SlidersVertical size={15} />
</button>
{/* Mini Player */}
<button
className="player-btn player-btn-sm"
onClick={() => invoke('open_mini_player').catch(() => {})}
aria-label="Mini Player"
data-tooltip="Mini Player"
>
<PictureInPicture2 size={15} />
</button>
{/* Volume */}
<div className="player-volume-section">
<button