쉐도잉 루프 고도화 — 한 줄 반복 / A-B 구간 / 자동 다음 줄을 폴링 하나로
import한 영상 화면에서 TubeShad급 쉐도잉을 만들었다. 한 줄 반복, A-B 구간 반복, 줄별 N회 자동 진행 + 0.5~1.5배속을, 단일 폴링 루프에 모드 분기로 통합했다.
목표
영상 import → 상세화면에서 그 자리에서 쉐도잉. 클립을 따로 만들지 않고, 자막 줄을 반복하고, 구간을 묶어 반복하고, 줄별로 N번씩 자동으로 내려가며 따라 말하기.
통합 모델 — 범위 + 플래그
세 기능을 세 갈래 코드로 짜면 엉킨다. 하나로 통합했다:
- loop = 줄 인덱스 범위
{a, b}(단일 줄이면a === b). - autoAdvance (자동 진행 토글) + reps (줄당 반복 횟수).
조작:
- 줄 1번 탭 →
{a:i, b:i}단일 줄 반복(다시 탭하면 해제). - "A-B" → 두 번 탭으로 A·B 지정 → 범위 반복.
- "자동 ▶" +
×N→ 범위를 한 줄씩 N번 반복하며 진행.
핵심 구현 — 폴링 하나, 모드 분기
YouTube IFrame은 영상 중간 경계에서 ENDED가 아니라 PAUSED를 쏜다. 그래서 onChangeState('ended')에 의존하면 짧은 구간이 다시 안 돈다. 해법은 getCurrentTime을 200ms로 폴링해서 직접 되감기. 단일 인터벌이 하이라이트 + 반복을 모두 구동한다:
- 자동 OFF: 위치가
lines[b].endMs를 지나면lines[a].startMs로 시크 → 블록 전체 반복. - 자동 ON:
lines[cursor]를repCount >= reps까지 반복하고, 그 다음cursor를 전진(b다음엔a로 래핑) → 줄별 드릴.
함정 + 패턴: 인터벌이 빠르게 바뀌는 UI 상태(loop/auto/reps/cursor)에 반응해야 하는데, 그 값들을 effect 의존성에 넣으면 매 변경마다 타이머가 재구독돼 덜덜거린다. → 인터벌은 안정적인 의존성(playing) 하나로만 걸고, 변하는 값은 ref로 읽는다(loopRef/autoRef/repsRef/cursorRef/repCountRef). 이게 "빠른 상태 + 타이머"의 정석.
거기에 라이브러리의 playbackRate prop으로 0.5~1.5배속(느리게 쉐도잉/빠르게 스킵)을 얹어 전부 조합 가능하게 했다. 예: A=첫줄·B=끝줄 + 자동 + ×3 + 0.75× = "영상 전체를 한 줄씩 3번, 0.75배속으로" 자동 쉐도잉.
커밋
c2d2205(줄 반복) → ded87ba(속도) → ee8d78f(A-B + 자동). 전부 모바일 JS만(백엔드 무변경), 컴파일/타입 통과.