25ed765a03
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
137 lines
5.8 KiB
Markdown
137 lines
5.8 KiB
Markdown
# Pyramid · Media Players (Flutter)
|
||
|
||
Player-Skelette im Pyramid-Stil — **Audio (4 Varianten)** und **Video (3 Varianten)** als wiederverwendbare Flutter-Widgets. Dies ist ein **UI-Gerüst ohne echte Medienwiedergabe** — `position`, `duration`, `buffered`, `playing` sind reine Props. Zum Anschluss an echte Medien siehe „Mit echtem Playback verbinden" weiter unten.
|
||
|
||
---
|
||
|
||
## 📁 Dateien
|
||
|
||
```
|
||
flutter_export/
|
||
├── pubspec.yaml
|
||
└── lib/
|
||
├── pyramid_theme.dart ← Farb-, Typo-, Radius-Tokens (Single Source of Truth)
|
||
├── pyramid_audio_player.dart ← PyramidAudioPlayer + AudioPlayerVariant
|
||
├── pyramid_video_player.dart ← PyramidVideoPlayer + VideoPlayerVariant
|
||
└── example_app.dart ← Showcase mit allen Varianten (zum Reinkopieren)
|
||
```
|
||
|
||
Kopiere die drei `pyramid_*.dart` Dateien in dein Projekt (z. B. `lib/widgets/players/`) und importiere sie wie üblich.
|
||
|
||
---
|
||
|
||
## 🎨 Theme
|
||
|
||
Alle Player ziehen Farben/Typo aus `PyramidTheme`. Default ist **Dark + Amber**.
|
||
|
||
```dart
|
||
PyramidTheme.dark = true; // false → Light
|
||
PyramidTheme.setAccent(42, 95, 58); // HSL — Default Pyramid Amber
|
||
// Weitere Akzente: 280/70/60 (Violet), 160/65/50 (Mint), 0/75/60 (Coral)
|
||
```
|
||
|
||
**Fonts:** Theme erwartet `Geist` und `GeistMono`. Ohne Asset-Einbindung in `pubspec.yaml` fällt Flutter auf System-Sans/Mono zurück (immer noch ok). Die Font-Asset-Konfig ist in `pubspec.yaml` auskommentiert vorbereitet.
|
||
|
||
---
|
||
|
||
## 🎧 Audio Player
|
||
|
||
```dart
|
||
PyramidAudioPlayer(
|
||
variant: AudioPlayerVariant.gracile,
|
||
title: 'test track',
|
||
artist: 'pyramid · demo',
|
||
duration: const Duration(minutes: 3, seconds: 24),
|
||
position: _position, // ValueNotifier oder State
|
||
playing: _playing,
|
||
onTogglePlay: () => setState(() => _playing = !_playing),
|
||
onSeek: (Duration to) => setState(() => _position = to),
|
||
onSkipBack: () { /* ‒15s */ },
|
||
onSkipForward: () { /* +15s */ },
|
||
onSpeedChange: (double s) { /* 1.0 / 1.5 / 2.0 */ },
|
||
)
|
||
```
|
||
|
||
### Wann welche Variante?
|
||
|
||
| Variante | Wann benutzen | Größe |
|
||
|---|---|---|
|
||
| **`AudioPlayerVariant.defaultBars`** | Standard-Audio-Anhang in einer Nachricht. Robuste Bar-Waveform — wirkt „solide", für Musik-Snippets. | ≥ 380px breit |
|
||
| **`AudioPlayerVariant.gracile`** ⭐ | **Empfehlung für Voice-Notes und längere Audio-Dateien.** Filigrane Hairline-Waveform — passt am besten zum Pyramid-Stil. | ≥ 380px breit |
|
||
| **`AudioPlayerVariant.compact`** | **In-Chat Inline-Embed**, wenn Platz knapp ist (Reply-Quote, Thread-Vorschau, Side-Panel). Eine Zeile. | ≥ 320px breit |
|
||
| **`AudioPlayerVariant.mono`** | Spezielle Räume mit Terminal-/Editorial-Identität (z. B. `#announce`, „archive", Devs-Spaces). Mono-Font, harte Kanten. Sparsam einsetzen. | ≥ 380px breit |
|
||
|
||
---
|
||
|
||
## 🎬 Video Player
|
||
|
||
```dart
|
||
PyramidVideoPlayer(
|
||
variant: VideoPlayerVariant.defaultHover,
|
||
title: 'test clip',
|
||
subtitle: 'pyramid · 1080p',
|
||
duration: const Duration(minutes: 4, seconds: 12),
|
||
position: _videoPos,
|
||
buffered: 0.65, // 0..1
|
||
playing: _playing,
|
||
onTogglePlay: () => setState(() => _playing = !_playing),
|
||
onSeek: (Duration to) => setState(() => _videoPos = to),
|
||
onSpeedChange: (double s) { /* 0.5 / 1 / 1.25 / 1.5 / 2 */ },
|
||
onQualityChange: (String q) { /* Auto / 1080p / 720p / 480p / 360p */ },
|
||
onFullscreen: () { /* Navigator push fullscreen route */ },
|
||
)
|
||
```
|
||
|
||
### Wann welche Variante?
|
||
|
||
| Variante | Wann benutzen | Aspect | Chrome |
|
||
|---|---|---|---|
|
||
| **`VideoPlayerVariant.defaultHover`** ⭐ | **Standard-Video-Embed im Chat.** Player nimmt volle Breite ein, Controls erscheinen bei Hover/Tap. | 16:10 | hover/auto-hide |
|
||
| **`VideoPlayerVariant.minimal`** | **Inline-Vorschau** in dichten Layouts (Reply, Thread-Liste). Controls immer sichtbar, kompakter. | 16:9 | always-on |
|
||
| **`VideoPlayerVariant.framed`** | **Galerie-/Karten-Layouts** oder Posts mit Caption darunter. Stage in Card, Controls auf hellem Card-BG außerhalb des schwarzen Stages. | 16:9 + Card | always-on (Card) |
|
||
|
||
---
|
||
|
||
## 🔌 Mit echtem Playback verbinden
|
||
|
||
Diese Widgets sind reine UI-Mockups. Für echte Wiedergabe binde z. B. das offizielle [`video_player`](https://pub.dev/packages/video_player) (Video) oder [`just_audio`](https://pub.dev/packages/just_audio) (Audio) Package an:
|
||
|
||
```dart
|
||
// Video-Beispiel mit video_player
|
||
final controller = VideoPlayerController.networkUrl(Uri.parse(url))..initialize();
|
||
controller.addListener(() => setState(() {}));
|
||
|
||
// im build():
|
||
PyramidVideoPlayer(
|
||
variant: VideoPlayerVariant.defaultHover,
|
||
title: 'remote clip',
|
||
duration: controller.value.duration,
|
||
position: controller.value.position,
|
||
buffered: controller.value.buffered.isEmpty ? 0
|
||
: controller.value.buffered.last.end.inMilliseconds /
|
||
controller.value.duration.inMilliseconds,
|
||
playing: controller.value.isPlaying,
|
||
onTogglePlay: () => controller.value.isPlaying ? controller.pause() : controller.play(),
|
||
onSeek: (to) => controller.seekTo(to),
|
||
onSpeedChange: (s) => controller.setPlaybackSpeed(s),
|
||
)
|
||
```
|
||
|
||
**Stage rendern:** Aktuell zeigt der Player einen Pyramid-Triangle als Platzhalter. Ersetze in `pyramid_video_player.dart` die `_stage()`-Methode durch dein `VideoPlayer(controller)` Widget.
|
||
|
||
---
|
||
|
||
## 🧪 Showcase
|
||
|
||
`example_app.dart` zeigt alle 7 Player-Varianten in einem scrollbaren Dark-Layout. Direkt ausführbar via `flutter run -t lib/example_app.dart`.
|
||
|
||
---
|
||
|
||
## ✅ Checklist beim Einbau
|
||
|
||
- [ ] `pyramid_theme.dart` + Audio + Video kopiert
|
||
- [ ] `PyramidTheme.dark` und `setAccent(...)` in App-State gesetzt
|
||
- [ ] (optional) Geist + Geist Mono in `pubspec.yaml` eingebunden
|
||
- [ ] Player mit echtem Audio-/Video-Controller verdrahtet
|
||
- [ ] Skip-Intervall an Produkt-Konvention angepasst (Material hat `replay_10` / `forward_10` — wenn du wirklich ±15s willst, die Icons mit `Stack(text)` ersetzen)
|