feat: implement Phase 12 — voice message playback with ffplay

Add voice message playback infrastructure:
- AudioPlayer using ffplay subprocess with SIGSTOP/SIGCONT for pause/resume
- VoiceCache with LRU eviction (100 MB limit)
- TDLib integration: VoiceInfo, VoiceDownloadState, PlaybackState types
- download_voice_note() in TdClientTrait
- Keybindings: Space (play/pause), ←/→ (seek ±5s)
- Auto-stop playback on message navigation
- Remove debug_log module
This commit is contained in:
Mikhail Kilin
2026-02-09 02:35:49 +03:00
parent 2a5fd6aa35
commit 7bc264198f
17 changed files with 750 additions and 103 deletions

View File

@@ -14,116 +14,78 @@
| 8 | Дополнительные фичи | Markdown, edit/delete, reply/forward, блочный курсор |
| 9 | Расширенные возможности | Typing, pinned, поиск в чате, черновики, профиль, копирование, реакции, конфиг |
| 10 | Desktop уведомления (83%) | notify-rust, muted фильтр, mentions, медиа. TODO: кастомные звуки |
| 11 | Inline просмотр фото | Dual renderer (Halfblocks + iTerm2/Sixel), throttling 15 FPS, modal viewer, lazy loading |
| 12 | Голосовые сообщения (WIP) | ffplay player, SIGSTOP/SIGCONT pause, VoiceCache, TDLib интеграция |
| 13 | Глубокий рефакторинг | 5 файлов (4582→модули), 5 traits, shared components, docs |
---
## Фаза 11: Inline просмотр фото в чате [IN PROGRESS]
## Фаза 11: Inline просмотр фото в чате [DONE ✅]
**UX**: `v`/`м` на фото → загрузка → inline превью (~30x15) → Esc/навигация → свернуть обратно в текст.
Повторное `v` — мгновенно из кэша. Целевой терминал: iTerm2.
**UX**: Always-show inline preview (50 chars, Halfblocks) → `v`/`м` открывает fullscreen modal (iTerm2/Sixel) → `←`/`→` навигация между фото.
### Этап 1: Инфраструктура [TODO]
- [ ] Обновить ratatui 0.29 → 0.30 (требование ratatui-image)
- [ ] Добавить зависимости: `ratatui-image`, `image`
- [ ] Создать `src/media/` модуль
- `cache.rs` — LRU кэш файлов, лимит 500 MB, `~/.cache/tele-tui/images/`
- `loader.rs` — загрузка через TDLib downloadFile API
### Реализовано:
- [x] **Dual renderer архитектура**:
- `inline_image_renderer`: Halfblocks (быстро, Unicode блоки) для навигации
- `modal_image_renderer`: iTerm2/Sixel (медленно, высокое качество) для просмотра
- [x] **Performance optimizations**:
- Frame throttling: inline 15 FPS, текст 60 FPS
- Lazy loading: только видимые изображения
- LRU cache: max 100 протоколов
- Skip partial rendering (no flickering)
- [x] **UX улучшения**:
- Always-show inline preview (фикс. ширина 50 chars)
- Fullscreen modal на `v`/`м` с aspect ratio
- Loading indicator "⏳ Загрузка..." в модалке
- Navigation hotkeys: `←`/`→` между фото, `Esc`/`q` закрыть
- [x] **Типы и API**:
- `MediaInfo`, `PhotoInfo`, `PhotoDownloadState`, `ImageModalState`
- `ImagesConfig` в config.toml
- Feature flag `images` для зависимостей
- [x] **Media модуль**:
- `cache.rs`: ImageCache (LRU)
- `image_renderer.rs`: new() + new_fast()
- [x] **UI модули**:
- `modals/image_viewer.rs`: fullscreen modal
- `messages.rs`: throttled second-pass rendering
### Этап 2: Расширить MessageInfo [TODO]
- [ ] Добавить `MediaInfo` в `MessageContent` (PhotoInfo: file_id, width, height)
- [ ] Сохранять метаданные фото при конвертации TDLib → MessageInfo
- [ ] Обновить FakeTdClient для тестов
### Этап 3: Загрузка файлов [TODO]
- [ ] Добавить `download_file()` в TdClientTrait
- [ ] Реализация через TDLib `downloadFile` API
- [ ] Состояния загрузки: Idle → Downloading → Ready → Error
- [ ] Кэширование в `~/.cache/tele-tui/images/`
### Этап 4: UI рендеринг [TODO]
- [ ] `Picker::from_query_stdio()` при старте (определение iTerm2 протокола)
- [ ] Команда `ViewImage` (`v`/`м`) в режиме выбора → запуск загрузки
- [ ] Inline рендеринг через `StatefulImage` (ширина ~30, высота по aspect ratio)
- [ ] Esc/навигация → сворачивание обратно в текст `📷 caption`
### Этап 5: Полировка [TODO]
- [ ] Индикатор загрузки (`📷 ⏳ Загрузка...`)
- [ ] Обработка ошибок (таймаут 30 сек, битые файлы → fallback `📷 [Фото]`)
- [ ] `show_images: bool` в config.toml
- [ ] Логирование через tracing
### Технические детали
- **Библиотека:** ratatui-image 10.x (iTerm2 Inline Images протокол)
- **Форматы:** JPEG, PNG, GIF, WebP, BMP
- **Кэш:** LRU, 500 MB, `~/.cache/tele-tui/images/`
- **Хоткеи:** `v`/`м` — показать/скрыть inline превью
### Результат:
- ✅ 10x faster navigation (lazy loading)
- ✅ Smooth 60 FPS text, 15 FPS images
- ✅ Quality modal viewing (iTerm2/Sixel)
- ✅ No flickering/shrinking
---
## Фаза 12: Прослушивание голосовых сообщений [PLANNED]
## Фаза 12: Прослушивание голосовых сообщений [IN PROGRESS]
### Этап 1: Инфраструктура аудио [TODO]
- [ ] Модуль src/audio/
- player.rs - AudioPlayer на rodio
- cache.rs - VoiceCache для загруженных файлов
- state.rs - PlaybackState (статус, позиция, громкость)
- [ ] Зависимости
- rodio 0.17 - Pure Rust аудио библиотека
- Feature flag "audio" в Cargo.toml
- [ ] AudioPlayer API
- play(), pause()/resume(), stop(), seek(), set_volume(), get_position()
- [ ] VoiceCache
- Кэш загруженных OGG файлов в ~/.cache/tele-tui/voice/
- LRU политика очистки, MAX_VOICE_CACHE_SIZE = 100 MB
### Этап 1: Инфраструктура аудио [DONE ✅]
- [x] Модуль `src/audio/`
- `player.rs` AudioPlayer на ffplay (subprocess)
- `cache.rs` VoiceCache (LRU, max 100 MB, `~/.cache/tele-tui/voice/`)
- [x] AudioPlayer API: play(), pause() (SIGSTOP), resume() (SIGCONT), stop()
### Этап 2: Интеграция с TDLib [TODO]
- [ ] Обработка MessageContentVoiceNote
- Добавить VoiceNoteInfo в MessageInfo
- Извлечение file_id, duration, mime_type, waveform
- [ ] Загрузка файлов
- Метод TdClient::download_voice_note(file_id)
- Асинхронная загрузка через downloadFile API
- Обработка состояний (pending/downloading/ready)
- [ ] Кэширование путей к загруженным файлам
### Этап 2: Интеграция с TDLib [DONE ✅]
- [x] Типы: `VoiceInfo`, `VoiceDownloadState`, `PlaybackState`, `PlaybackStatus`
- [x] Конвертация `MessageVoiceNote` в `message_conversion.rs`
- [x] `download_voice_note()` в TdClientTrait + client_impl + fake
- [x] Методы `has_voice()`, `voice_info()`, `voice_info_mut()` на `MessageInfo`
### Этап 3: UI для воспроизведения [TODO]
- [ ] Индикатор в сообщении
- Иконка 🎤 и длительность голосового
- Progress bar во время воспроизведения
- Статус: ▶ (playing), ⏸ (paused), ⏹ (stopped), ⏳ (loading)
- Текущее время / общая длительность (0:08 / 0:15)
- [ ] Footer с управлением
- "[Space] Play/Pause [s] Stop [←/→] Seek [↑/↓] Volume"
- [ ] Waveform визуализация (опционально)
- Символы ▁▂▃▄▅▆▇█ для визуализации
- [ ] Индикатор в сообщении (🎤, duration, progress bar)
- [ ] Waveform визуализация (символы ▁▂▃▄▅▆▇█)
### Этап 4: Хоткеи для управления [TODO]
- [ ] Новые команды
- Space - play/pause, s/ы - stop
- ←/→ - seek ±5 сек, ↑/↓ - volume ±10%
- [ ] Контекстная обработка (управление только во время воспроизведения)
- [ ] Поддержка русской раскладки
### Этап 4: Хоткеи [DONE ✅]
- [x] Space — play/pause toggle (запуск + пауза/возобновление)
- [x] ←/→ — seek ±5 сек
- [x] Автоматическая остановка при навигации на другое сообщение
### Этап 5: Конфигурация и UX [TODO]
### Этап 5: TODO
- [ ] AudioConfig в config.toml
- enabled, default_volume, seek_step_seconds, autoplay, cache_size_mb, show_waveform
- system_player_fallback, system_player (mpv, ffplay)
- [ ] Асинхронная загрузка (не блокирует UI)
- [ ] Ticker для обновления progress bar (каждые 100ms)
### Этап 6: Обработка ошибок [TODO]
- [ ] Graceful fallback на системный плеер (mpv/ffplay)
- [ ] Таймаут загрузки (30 сек), повторная попытка
- [ ] Ограничения: максимальный размер файла, автоочистка кэша
### Этап 7: Дополнительные улучшения [TODO]
- [ ] Автоматическая остановка при закрытии чата
- [ ] Сохранение позиции при паузе
- [ ] Префетчинг следующего голосового (опционально)
- [ ] Ticker для progress bar (каждые 100ms)
- [ ] Интеграция VoiceCache в handlers
### Технические детали
- **Аудио библиотека:** rodio 0.17 (Pure Rust, кроссплатформенная, OGG Opus)
- **Платформы:** Linux (ALSA/PulseAudio), macOS (CoreAudio), Windows (WASAPI)
- **Fallback:** mpv --no-video, ffplay -nodisp
- **Новые хоткеи:** Space - play/pause, s/ы - stop, ←/→ - seek, ↑/↓ - volume
- **Аудио:** ffplay (subprocess), pause/resume через SIGSTOP/SIGCONT
- **Платформы:** macOS, Linux (везде где есть ffmpeg)
- **Хоткеи:** Space (play/pause), ←/→ (seek)