재생바 총길이 안정화 — ffprobe 메타 우선 + 없으면 즉석 ffprobe
증상: 영상 재생 시 재생바가 실시간으로 늘어나며 요동침. 총 재생시간이 고정되지 않음. 원인: durationSec가 직접 모드에서 <video>.duration을 우선 사용했는데, 스트림/ 일부 컨테이너(리먹스 fMP4 등)에선 재생 중 요소 duration이 계속 커져 재생바가 current/duration 비율로 요동쳤다. 또 메타(duration_ms)가 아직 추출 안 된 영상은 playback_info가 null을 반환해 요소 duration에 의존. 수정: - VideoPlayer.durationSec: ffprobe 메타 총길이를 항상 우선, 없을 때만 요소 duration 폴백. (고정 총길이라 재생바 안정) - playback_info: 영상인데 duration_ms가 null이면 즉석 ffprobe로 채우고 DB에 캐시 (SMB 등 메타 미추출 상태에서도 정확한 총길이/코덱 보장, 다음 재생부터 재프로브 없음). 검증(VP9 20s): playback_info durationMs=20000, 재생 중 표시 0:00→ 진행하며 총길이는 0:20 고정, 진행바 0%→4%→8% 단조 증가(요동 없음). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+55
-12
@@ -1286,26 +1286,69 @@ pub struct PlaybackInfo {
|
||||
#[tauri::command]
|
||||
pub fn playback_info(state: tauri::State<'_, AppState>, file_id: i64) -> CmdResult<PlaybackInfo> {
|
||||
let stream_available = state.tools.is_some();
|
||||
state
|
||||
// (info, kind, 파일경로) — 경로는 메타 미추출 시 즉석 ffprobe용
|
||||
let (mut info, kind, path): (PlaybackInfo, i64, std::path::PathBuf) = state
|
||||
.db
|
||||
.with_read(move |conn| {
|
||||
conn.query_row(
|
||||
"SELECT ext, vcodec, acodec, duration_ms, width, height FROM files WHERE id = ?1",
|
||||
"SELECT f.ext, f.vcodec, f.acodec, f.duration_ms, f.width, f.height, f.kind, fo.path, f.name
|
||||
FROM files f JOIN folders fo ON fo.id = f.folder_id WHERE f.id = ?1",
|
||||
[file_id],
|
||||
|r| {
|
||||
Ok(PlaybackInfo {
|
||||
ext: r.get(0)?,
|
||||
vcodec: r.get(1)?,
|
||||
acodec: r.get(2)?,
|
||||
duration_ms: r.get(3)?,
|
||||
width: r.get(4)?,
|
||||
height: r.get(5)?,
|
||||
stream_available,
|
||||
})
|
||||
let dir: String = r.get(7)?;
|
||||
let name: String = r.get(8)?;
|
||||
Ok((
|
||||
PlaybackInfo {
|
||||
ext: r.get(0)?,
|
||||
vcodec: r.get(1)?,
|
||||
acodec: r.get(2)?,
|
||||
duration_ms: r.get(3)?,
|
||||
width: r.get(4)?,
|
||||
height: r.get(5)?,
|
||||
stream_available,
|
||||
},
|
||||
r.get::<_, i64>(6)?,
|
||||
std::path::Path::new(&dir).join(name),
|
||||
))
|
||||
},
|
||||
)
|
||||
})
|
||||
.map_err(err_str)
|
||||
.map_err(err_str)?;
|
||||
|
||||
// 영상인데 메타(재생시간)가 아직 없으면 즉석 ffprobe로 채운다.
|
||||
// 재생바 총길이가 요소 duration(스트림 중 증가)에 의존하지 않도록 정확한 총길이를 보장.
|
||||
if info.duration_ms.is_none() && kind == 1 {
|
||||
if let Some(tools) = &state.tools {
|
||||
if let Ok(mi) = archive_indexer::ffmpeg::probe(tools, &path) {
|
||||
info.duration_ms = mi.duration_ms;
|
||||
if info.vcodec.is_none() {
|
||||
info.vcodec = mi.vcodec.clone();
|
||||
}
|
||||
if info.acodec.is_none() {
|
||||
info.acodec = mi.acodec.clone();
|
||||
}
|
||||
if info.width.is_none() {
|
||||
info.width = mi.width;
|
||||
}
|
||||
if info.height.is_none() {
|
||||
info.height = mi.height;
|
||||
}
|
||||
// DB에 캐시 (같은 영상 재생 시 SMB 재프로브 방지). meta_state 미완이면 완료 표시.
|
||||
let (d, vc, ac, w, h) = (mi.duration_ms, mi.vcodec, mi.acodec, mi.width, mi.height);
|
||||
let _ = state.db.with_write(move |conn| {
|
||||
conn.execute(
|
||||
"UPDATE files SET duration_ms=COALESCE(duration_ms,?2),
|
||||
vcodec=COALESCE(vcodec,?3), acodec=COALESCE(acodec,?4),
|
||||
width=COALESCE(width,?5), height=COALESCE(height,?6),
|
||||
meta_state=CASE WHEN meta_state=0 THEN 1 ELSE meta_state END
|
||||
WHERE id=?1",
|
||||
rusqlite::params![file_id, d, vc, ac, w, h],
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
|
||||
Reference in New Issue
Block a user