feat: complete Phase 12 — voice playback ticker, cache, config, and UI
Add playback position ticker in event loop with 1s UI refresh rate, integrate VoiceCache for downloaded voice files, add [audio] config section (cache_size_mb, auto_download_voice), and render progress bar with waveform visualization in message bubbles. Fix race conditions in AudioPlayer: add `starting` flag to prevent false `is_stopped()` during ffplay startup, guard pid cleanup so old threads don't overwrite newer process pids. Implement `resume_from()` with ffplay `-ss` for real audio seek on unpause (-1s rewind). Kill ffplay on app exit via `stop_playback()` in shutdown + Drop impl. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
39
src/main.rs
39
src/main.rs
@@ -167,6 +167,42 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
|
||||
// Обновляем позицию воспроизведения голосового сообщения
|
||||
{
|
||||
let mut stop_playback = false;
|
||||
if let Some(ref mut playback) = app.playback_state {
|
||||
use crate::tdlib::PlaybackStatus;
|
||||
match playback.status {
|
||||
PlaybackStatus::Playing => {
|
||||
let prev_second = playback.position as u32;
|
||||
if let Some(last_tick) = app.last_playback_tick {
|
||||
let delta = last_tick.elapsed().as_secs_f32();
|
||||
playback.position += delta;
|
||||
}
|
||||
app.last_playback_tick = Some(std::time::Instant::now());
|
||||
|
||||
// Проверяем завершение воспроизведения
|
||||
if playback.position >= playback.duration
|
||||
|| app.audio_player.as_ref().map_or(false, |p| p.is_stopped())
|
||||
{
|
||||
stop_playback = true;
|
||||
}
|
||||
// Перерисовка только при смене секунды (не 60 FPS)
|
||||
if playback.position as u32 != prev_second || stop_playback {
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
app.last_playback_tick = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if stop_playback {
|
||||
app.stop_playback();
|
||||
app.last_playback_tick = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Рендерим только если есть изменения
|
||||
if app.needs_redraw {
|
||||
terminal.draw(|f| ui::render(f, app))?;
|
||||
@@ -185,6 +221,9 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
// Graceful shutdown
|
||||
should_stop.store(true, Ordering::Relaxed);
|
||||
|
||||
// Останавливаем воспроизведение голосового (убиваем ffplay)
|
||||
app.stop_playback();
|
||||
|
||||
// Закрываем TDLib клиент
|
||||
let _ = tdlib_rs::functions::close(app.td_client.client_id()).await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user