docs: add v0.2.0 player mode design spec
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
331
docs/superpowers/specs/2026-09-22-v020-player-mode-design.md
Normal file
331
docs/superpowers/specs/2026-09-22-v020-player-mode-design.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# v0.2.0 — Player Mode Design Spec
|
||||
|
||||
**Date:** 2026-09-22
|
||||
**Version target:** 0.2.0
|
||||
**Scope:** Add a dedicated "Player" mode alongside the existing "Clipper" mode, transforming the app into a clean, minimal video player when clipping isn't needed.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The app currently operates exclusively as a video clipper. This spec adds a second mode — **Player** — that strips away clipping UI and presents a focused video playback experience (think QuickTime / VLC). Users toggle between modes with a toolbar button or keyboard shortcut; the switch preserves all session state (playhead position, loaded video, clips, waveform data).
|
||||
|
||||
**Architecture approach:** Single `App.svelte` with conditional rendering. The `<video>` element inside `VideoPlayer.svelte` is never destroyed during mode switches — only the surrounding chrome changes. New Player-specific components (`PlayerControls`, `PlayerTimeline`, `SpeedSelector`) stay focused and small.
|
||||
|
||||
---
|
||||
|
||||
## 2. State & Preferences
|
||||
|
||||
### New preference field
|
||||
|
||||
Add `appMode: 'clipper' | 'player'` to the `Preferences` interface in `preferences.svelte.ts`. Default: `'clipper'`. Persisted via the Tauri store so it survives relaunches.
|
||||
|
||||
### New store helper
|
||||
|
||||
- `setAppMode(mode: 'clipper' | 'player')` — updates `preferences.appMode` and saves.
|
||||
|
||||
### Mode switching invariant
|
||||
|
||||
Toggling between modes **preserves all session state**: playhead position, loaded video, playback state (playing/paused), clips, waveform data, captions, volume, speed — everything. The `<video>` element itself never unmounts, so playback state is inherently preserved (same principle as the existing clip list layout toggle).
|
||||
|
||||
---
|
||||
|
||||
## 3. Keyboard Shortcuts
|
||||
|
||||
### New shortcuts
|
||||
|
||||
| Key | Action | Available in |
|
||||
|-----|--------|-------------|
|
||||
| `p` | Switch to Player mode | Both modes |
|
||||
| `c` | Switch to Clipper mode | Both modes |
|
||||
| `f` | Toggle fullscreen | Player mode only |
|
||||
|
||||
### Existing shortcuts by mode
|
||||
|
||||
| Key | Action | Clipper | Player |
|
||||
|-----|--------|---------|--------|
|
||||
| `Space` / `k` | Play/Pause | ✓ | ✓ |
|
||||
| `←` / `→` | Seek ±5s (±1s with Shift) | ✓ | ✓ |
|
||||
| `,` / `.` | Frame step back/forward (works when paused) | ✓ | ✓ |
|
||||
| `Shift+,` / `Shift+.` | Keyframe jump | ✓ | ✓ |
|
||||
| `j` / `l` | Shuttle speed down/up | ✓ | ✓ |
|
||||
| `i` / `o` | Mark in/out point | ✓ | No-op |
|
||||
| `Delete` / `Backspace` | Delete selected clip | ✓ | No-op |
|
||||
| `Cmd+E` | Export dialog | ✓ | No-op |
|
||||
|
||||
---
|
||||
|
||||
## 4. App Layout
|
||||
|
||||
### Clipper mode (unchanged)
|
||||
|
||||
Renders exactly as v0.1.x: toolbar → video player → transport controls → resizable timeline → clip list (bottom or sidebar).
|
||||
|
||||
### Player mode
|
||||
|
||||
The layout collapses to a single full-bleed video area with auto-hiding overlays:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────┐
|
||||
│ Toolbar (auto-hides) │ ← slides down from top on cursor proximity
|
||||
├──────────────────────────────────┤
|
||||
│ │
|
||||
│ VideoPlayer │ ← fills all available space
|
||||
│ (floating controls overlay) │
|
||||
│ │
|
||||
├──────────────────────────────────┤
|
||||
│ PlayerTimeline (auto-hides) │ ← slides up from bottom on cursor proximity
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Hidden in Player mode:**
|
||||
- `TransportControls` bar (replaced by floating `PlayerControls`)
|
||||
- `ClipList` (both bottom and sidebar variants)
|
||||
- Timeline resize handle
|
||||
- Minimap
|
||||
- Timeline clip rendering
|
||||
- I/O mark buttons
|
||||
|
||||
**Visible in Player mode:**
|
||||
- `VideoPlayer.svelte` (unchanged, always mounted)
|
||||
- `StatusBar` at the bottom (stays visible in both modes)
|
||||
- `PlayerControls` (floating overlay)
|
||||
- `PlayerTimeline` (auto-hiding simplified timeline)
|
||||
|
||||
**CSS:** Player mode sets a different `grid-template` on `.content` that gives the video area `1fr` and collapses the lower/clip-list rows to `0`. The auto-hiding panels use `position: absolute` within the app shell.
|
||||
|
||||
---
|
||||
|
||||
## 5. Mode Toggle Button
|
||||
|
||||
- **Position:** In the toolbar, between the URL input and the existing layout toggle button.
|
||||
- **Appearance:** Text label showing current mode — `Clipper` or `Player` — styled as a small pill/badge button.
|
||||
- **Click:** Toggles between modes.
|
||||
- **Tooltip:** "Switch to Player (P)" or "Switch to Clipper (C)".
|
||||
|
||||
---
|
||||
|
||||
## 6. Floating Player Controls (`PlayerControls.svelte`)
|
||||
|
||||
An absolutely-positioned overlay rendered over the video area. Designed as a semi-transparent frosted-glass panel.
|
||||
|
||||
### Show/hide logic
|
||||
|
||||
- **Show** when: mouse moves anywhere over the video area, OR video is paused.
|
||||
- **Hide** when: mouse is still for 2.5 seconds AND video is playing.
|
||||
- **Transition:** fade in/out over ~200ms using CSS opacity + pointer-events.
|
||||
- **Fullscreen:** Same show/hide behavior applies when video is in fullscreen mode.
|
||||
|
||||
### Panel layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ▁▂▃▅▃▂▁▂▃▅▇▅▃▂▁▂▃▅▃▂▁ ●─────────────────── │ ← seek bar
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ▶❚❚ ◀◀ ▶▶ 🔊━━━━ 0:32 / 4:15 1.0× CC ⧉ ⛶ │ ← controls row
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Top row — Seek bar
|
||||
|
||||
- Full-width thin progress bar spanning the entire panel width.
|
||||
- Shows playback progress as a filled track (`var(--accent)`) against a dark unfilled track.
|
||||
- Hovering shows a time tooltip above the cursor position.
|
||||
- Click or drag to seek (reuses `seekTo()` from `playback.ts`).
|
||||
- Track height: 4px, expanding to 6px on hover (CSS transition).
|
||||
- Thumb: 12px circle, visible only on hover.
|
||||
|
||||
#### Bottom row — Controls
|
||||
|
||||
Three groups arranged with flexbox `space-between`:
|
||||
|
||||
**Left group (playback):**
|
||||
- Play/Pause — primary action button, `▶` / `❚❚`
|
||||
- Skip back 10s — `⏪` button, calls `seekBy(-10)`
|
||||
- Skip forward 10s — `⏩` button, calls `seekBy(10)`
|
||||
|
||||
**Center group (time):**
|
||||
- Time display: `0:32 / 4:15` in monospace font (`var(--font-mono)`), slightly dimmed
|
||||
|
||||
**Right group (settings):**
|
||||
- Speed — shows current rate (e.g. `1×`, `1.5×`). Click opens `SpeedSelector` popup. Sets `videoElement.playbackRate` directly.
|
||||
- CC — `CC` text button, highlighted when captions active. Click toggles captions. Right-click opens `CaptionSettingsPanel`.
|
||||
- PiP — `⧉` icon. Click enters/exits Picture-in-Picture. Active state: accent color.
|
||||
- Fullscreen — `⛶` icon. Click enters/exits fullscreen. Swaps to exit icon when active.
|
||||
|
||||
### Visual styling
|
||||
|
||||
- **Background:** `rgba(0, 0, 0, 0.75)` with `backdrop-filter: blur(8px)` (frosted glass)
|
||||
- **Border radius:** `12px` on top corners only (bottom flush with video edge)
|
||||
- **Padding:** `12px 16px`, `8px` gap between rows
|
||||
- **Buttons:** Borderless, icon-only, `color: var(--text-primary)`, hover: `color: var(--accent)`. ~32px tap targets.
|
||||
- **Pointer events:** Panel has `pointer-events: auto`; surrounding overlay has `pointer-events: none` so clicks pass through to the video.
|
||||
|
||||
### Video click/double-click behavior
|
||||
|
||||
- Single click on video (outside controls) → toggles play/pause
|
||||
- Double-click on video → toggles fullscreen
|
||||
|
||||
---
|
||||
|
||||
## 7. Speed Selector (`SpeedSelector.svelte`)
|
||||
|
||||
A small vertical popup menu triggered by clicking the speed button in `PlayerControls`.
|
||||
|
||||
- Options: 0.5×, 0.75×, 1×, 1.25×, 1.5×, 2×
|
||||
- Current rate has a checkmark indicator
|
||||
- Click an option to set rate, popup closes
|
||||
- Click outside popup to dismiss
|
||||
- Popup appears above the speed button, anchored to its position
|
||||
- Sets `videoElement.playbackRate` directly
|
||||
- JKL shuttle keys continue to work independently (they adjust `playbackRate` too)
|
||||
|
||||
---
|
||||
|
||||
## 8. Simplified Player Timeline (`PlayerTimeline.svelte`)
|
||||
|
||||
A lightweight timeline component for Player mode. Shows only waveform and timestamps — no clips, thumbnails, clip handles, or minimap.
|
||||
|
||||
### Auto-hide behavior
|
||||
|
||||
- **Show** when cursor is within ~80px of the bottom edge of the video area.
|
||||
- **Hide** when cursor moves away from bottom edge AND video is playing.
|
||||
- **Stays visible** when video is paused.
|
||||
- **Transition:** slides up from below + fades in over ~200ms.
|
||||
- **Relationship to floating controls:** The floating controls and the player timeline have overlapping but distinct triggers. The floating controls show on *any* mouse movement over the video area; the player timeline shows only on bottom-edge proximity (~80px). When the cursor is near the bottom, both are visible and they appear together as a cohesive unit. When the cursor moves elsewhere over the video, only the floating controls appear (not the timeline).
|
||||
|
||||
### Layout
|
||||
|
||||
- **Fixed height:** ~50px (not user-resizable)
|
||||
- **Position:** Bottom of the player area, below the floating controls panel
|
||||
- **Background:** Semi-transparent dark, matching floating controls (`rgba(0, 0, 0, 0.6)`, `backdrop-filter: blur(4px)`)
|
||||
|
||||
### Rendering
|
||||
|
||||
- **Waveform:** Reuses `drawWaveform()` from `waveformRenderer.ts`, rendered at full duration (always zoom level 1, no pan).
|
||||
- **Timestamps:** Reuses timestamp tick rendering from `renderer.ts`, simplified to major ticks only (no clip lanes).
|
||||
- **Playhead:** Vertical white line showing current position.
|
||||
- **Click-to-seek:** Clicking anywhere seeks to that position.
|
||||
- **No zoom/pan:** Always shows the full duration.
|
||||
|
||||
---
|
||||
|
||||
## 9. Toolbar Auto-Hide (Player Mode)
|
||||
|
||||
### Clipper mode
|
||||
|
||||
Toolbar always visible (no change).
|
||||
|
||||
### Player mode
|
||||
|
||||
- **Show** when cursor is within ~50px of the top edge of the window.
|
||||
- **Hide** when cursor moves away from top edge AND video is playing.
|
||||
- **Stays visible** when video is paused.
|
||||
- **Transition:** slides down from above + fades in (~200ms), matching the bottom HUD timing.
|
||||
- **Content:** Same as Clipper mode (URL input, mode toggle, about, preferences).
|
||||
|
||||
---
|
||||
|
||||
## 10. Picture-in-Picture
|
||||
|
||||
- **API:** `HTMLVideoElement.requestPictureInPicture()` — uses the native macOS PiP window via WKWebView.
|
||||
- **Scope:** Player mode only. No PiP button in Clipper mode.
|
||||
- **Button:** `⧉` icon in `PlayerControls`. Active state shown with accent color.
|
||||
- **Events:** Listens for `enterpictureinpicture` and `leavepictureinpicture` on the video element to track state.
|
||||
- **Exiting PiP:** Returns focus to the app window.
|
||||
|
||||
### New playback helpers
|
||||
|
||||
Add to `playback.ts`:
|
||||
- `requestPiP()` — calls `videoElement.requestPictureInPicture()`
|
||||
- `exitPiP()` — calls `document.exitPictureInPicture()`
|
||||
- `isPiPActive(): boolean` — checks `document.pictureInPictureElement`
|
||||
|
||||
---
|
||||
|
||||
## 11. Fullscreen
|
||||
|
||||
- **API:** `videoElement.requestFullscreen()` with fallback to `videoElement.webkitEnterFullscreen()` for WKWebView compatibility. WKWebView's webkit-prefixed API fullscreens the video natively (similar to Safari's behavior).
|
||||
- **Scope:** Player mode only. Keyboard shortcut `f`.
|
||||
- **Button:** `⛶` icon in `PlayerControls`. Swaps to exit-fullscreen icon when active.
|
||||
- **Events:** Listens for `fullscreenchange` to track state.
|
||||
- **Controls in fullscreen:** The floating `PlayerControls` overlay remains functional in fullscreen mode with the same auto-hide behavior.
|
||||
|
||||
### New playback helpers
|
||||
|
||||
Add to `playback.ts`:
|
||||
- `toggleFullscreen()` — enters or exits fullscreen on the video element
|
||||
- `isFullscreenActive(): boolean` — checks `document.fullscreenElement`
|
||||
|
||||
---
|
||||
|
||||
## 12. Transitions (FLIP Animations)
|
||||
|
||||
### Clipper ↔ Player mode switch
|
||||
|
||||
1. **Video player area** — FLIP animation: the video container smoothly expands to fill the full area (Clipper→Player) or shrinks back to its grid cell (Player→Clipper). Manual FLIP: snapshot bounding rect before mode change, apply mode, snapshot new rect, animate from old→new using CSS `transform`.
|
||||
|
||||
2. **Transport controls / Clip list** — fade + slide out downward when switching to Player, fade + slide back in when returning to Clipper. Duration ~250ms, eased.
|
||||
|
||||
3. **Floating controls** — fade in after video expansion completes (staggered ~100ms delay).
|
||||
|
||||
4. **Toolbar** — slides up and fades out (Clipper→Player), slides back down (Player→Clipper).
|
||||
|
||||
### Implementation approach
|
||||
|
||||
- CSS custom properties + `transition` for grid layout changes (grid rows/columns animate in modern browsers).
|
||||
- Svelte `transition:` directives (`fly`, `fade`, `slide`) for elements entering/leaving the DOM.
|
||||
- Manual FLIP on the video container for the expansion/contraction.
|
||||
- Total transition budget: ~300ms.
|
||||
|
||||
### Edge cases
|
||||
|
||||
- Mid-transition mode switch: new transition takes over from current interpolated position (CSS transitions handle this naturally).
|
||||
- No video loaded: transition still runs (placeholder area expands/contracts).
|
||||
|
||||
---
|
||||
|
||||
## 13. New & Modified Files
|
||||
|
||||
### New files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/components/PlayerControls.svelte` | Floating controls overlay (seek bar, play/pause, volume, speed, CC, PiP, fullscreen, time display) |
|
||||
| `src/lib/components/PlayerTimeline.svelte` | Simplified auto-hiding timeline (waveform + timestamps only) |
|
||||
| `src/lib/components/SpeedSelector.svelte` | Speed popup menu used by PlayerControls |
|
||||
|
||||
### Modified files
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/lib/stores/preferences.svelte.ts` | Add `appMode` field, `setAppMode()`, persist/load |
|
||||
| `src/App.svelte` | Mode toggle button, conditional layout, auto-hide toolbar, `p`/`c`/`f` shortcuts, FLIP transitions |
|
||||
| `src/lib/components/VideoPlayer.svelte` | PiP/fullscreen event listeners, expose PiP/fullscreen state, conditional CC toggle rendering (Clipper only; Player delegates to PlayerControls) |
|
||||
| `src/lib/transport/playback.ts` | Add `requestPiP()`, `exitPiP()`, `isPiPActive()`, `toggleFullscreen()`, `isFullscreenActive()`, `setPlaybackRate()` |
|
||||
| `src/app.css` | CSS variables/classes for player mode transitions, auto-hide panels |
|
||||
|
||||
### Unchanged files
|
||||
|
||||
- `Timeline.svelte` — used only in Clipper mode
|
||||
- `ClipList.svelte` — hidden in Player mode
|
||||
- `TransportControls.svelte` — hidden in Player mode
|
||||
- All Rust backend code — no backend changes
|
||||
- `StatusBar.svelte` — visible in both modes
|
||||
|
||||
### Testing
|
||||
|
||||
- Existing tests unaffected (timeline interactions, clips store, time utilities).
|
||||
- New tests: PlayerControls show/hide logic, speed selector state, mode switching preserves session state.
|
||||
|
||||
---
|
||||
|
||||
## 14. Version Bump
|
||||
|
||||
Bump version from `0.1.3` to `0.2.0` in:
|
||||
- `package.json`
|
||||
- `src-tauri/tauri.conf.json`
|
||||
- `src-tauri/Cargo.toml`
|
||||
- `VERSION` file
|
||||
|
||||
Use the existing `scripts/bump-version.sh` if it handles all locations, otherwise update manually.
|
||||
Reference in New Issue
Block a user