djmd 프레임 동기·오버레이 지면고정·선형-측점 정합·UI 단축키
- djmd(영상 내장 텔레메트리) 프레임 동기 위치/고도: MP4 stbl 선별 파싱(djmdTrack.ts), CSV 시각정렬 오차(평균 3.2m) 제거 — 세그먼트별 백그라운드 정밀화(frameSynced 플래그) - 자세 시간축 재정렬: djmd 위치 교차상관으로 CSV↔영상 오프셋 자동 추정(실측 -0.33s, 잔차 0.24m) 후 yaw/pitch/roll 재샘플(yaw 최단각) — 옆 지물 미끄러짐 해소 - 포즈 평활 자동 제한: 프레임 동기 데이터는 ±5프레임 상한(기존 ±60=±1s) — 회전 시 오버레이 전체가 드론에 딸려오는 지연 제거 - 선형-측점 정합: 측점 위치를 선형 정점으로 삽입(3D 이격≤0.012m) + 정점 z 를 측점 체이니지 보간으로 재정렬, 측점 라벨 화면 EMA 제거(선과 동일한 즉시 투영) — 선형이 측점 POI 를 픽셀 단위로 통과 - 드론높이 모드 높이 규칙 통일: 선형도 '드론고도-이격거리' 평면 적용(라벨과 동일) - 카메라 offZ 잔재 교정: 표고 체계 정비 이전 드래그 보정값(-18.3m)이 이중 보정이 되어 원거리 POI 가 접근할수록 밀리던 원인 — 경로 1-1 camera.json 0 으로 교정(.bak 백업) - 로드 UX: 폴더 로드 시 정지 상태로 대기(처음부터), 데이터 교체 시 UI 초기 상태 복원 - 라벨: 측점 팝업 표고(타원체고) 행 추가, 구조물-POI 이중 라벨 제거(어음천교), 시간동기 보정 슬라이더(timeOffsetSec) - 측점 표기 0+000 형식 전환(생성 6곳·파서 4곳 신구 호환) + 스테이션바 양끝 100m 내 시설물 없으면 시작/끝 스테이션 마크 - 단축키: F2 개발자 모드(dev_mode 1↔0, 세션 한정), A 선형 표시 토글 - 인프라: 54000 포트 재부팅 자동화(portproxy 직접 IP + 로그온 갱신, pm2 resurrect) - docs/history: 작업 이력 23건 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,7 +76,7 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadLocalFile = useCallback((file: File) => {
|
||||
const loadLocalFile = useCallback((file: File, autoPlay = false) => {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
@@ -87,19 +87,32 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
|
||||
// 재생 중 폴더 교체 지원: 새 src 설정 후 이전 blob URL 을 해제한다.
|
||||
// (player.src() 가 새 소스를 채택한 뒤이므로 prevUrl 은 더 이상 참조되지 않아 안전)
|
||||
const prevUrl = objectUrlRef.current;
|
||||
// 교체 직전 재생 중이었으면(=드롭 교체) 새 영상도 이어서 재생. 최초 로드는 prevUrl 없음 → 자동재생 안 함.
|
||||
const wasPlaying = prevUrl != null && !player.paused();
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
objectUrlRef.current = objectUrl;
|
||||
player.src({ src: objectUrl, type: file.type || 'video/mp4' });
|
||||
// 로드 정책: 로딩 완료(loadedmetadata) 시점에
|
||||
// - autoPlay=false(폴더 로드): '정지' 명령 실행 — pause + 0초. 애매한 일시정지 상태 방지,
|
||||
// 재생 버튼/스페이스 등 사용자 조작으로만 시작
|
||||
// - autoPlay=true(분할영상 연속재생·재생목록 선택): 0초부터 즉시 재생
|
||||
player.one('loadedmetadata', () => {
|
||||
if (player.isDisposed() || objectUrlRef.current !== objectUrl) return;
|
||||
player.currentTime(0);
|
||||
if (autoPlay) {
|
||||
const p = player.play();
|
||||
if (p && typeof p.catch === 'function') p.catch(() => {});
|
||||
} else {
|
||||
player.pause();
|
||||
}
|
||||
});
|
||||
store.setSource({ kind: 'local', file, objectUrl });
|
||||
store.setHlsReady(false);
|
||||
// 데이터 교체 시 UI 상태 즉시 초기화 — 이전 소스의 재생중 표시(일시정지 토글)·시간이
|
||||
// 새 소스 메타데이터 로드 전까지 남아 보이는 문제 방지. (src 교체는 pause 이벤트를
|
||||
// 보장하지 않아 store.playing 이 true 로 남을 수 있음)
|
||||
store.setPlaying(false);
|
||||
store.setCurrentTime(0);
|
||||
store.setDuration(0);
|
||||
if (prevUrl) URL.revokeObjectURL(prevUrl);
|
||||
if (wasPlaying) {
|
||||
// 드롭은 사용자 제스처라 자동재생 정책에 막히지 않음. 준비되면 재생(실패는 무시).
|
||||
const p = player.play();
|
||||
if (p && typeof p.catch === 'function') p.catch(() => {});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -117,6 +130,10 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
|
||||
|
||||
store.setSource({ kind: 'server', videoId, filename });
|
||||
store.setHlsReady(false);
|
||||
// 데이터 교체 시 UI 상태 즉시 초기화 (loadLocalFile 과 동일 정책)
|
||||
store.setPlaying(false);
|
||||
store.setCurrentTime(0);
|
||||
store.setDuration(0);
|
||||
const playRaw = () => player.src({ src: `/api/stream/${videoId}`, type: 'video/mp4' });
|
||||
|
||||
// 코덱 확인 — 브라우저가 직접 못 푸는 코덱(HEVC 등)이면 HLS로 자동 재생
|
||||
|
||||
Reference in New Issue
Block a user