비디오 연동
TapKit과 비디오 플레이어 동기화
비디오 연동
AI 튜터가 현재 강의 재생 시점을 인식하고, 채팅에서 특정 구간을 클릭하면 비디오가 자동으로 이동하도록 연동합니다.
비디오 연동을 권장하는 이유
- AI가 현재 재생 시점 기반으로 더 정확한 답변 제공
- 채팅에서 특정 구간 클릭 시 비디오 자동 이동
- 강의 변경(
setCourse) 시 자동으로 새 클립 추적
기본 사용법 (HTMLVideoElement)
가장 간단한 방법은 videoTarget 속성에 <video> 요소를 전달하는 것입니다.
<video id="lecture-video" src="/lecture.mp4" controls></video>
<tap-kit user-id="user-1" course-id="course-1" clip-id="clip-1"></tap-kit>
<script>
const kit = document.querySelector('tap-kit');
kit.apiKey = 'your-api-key';
kit.videoTarget = document.getElementById('lecture-video');
</script>React에서는 useTapKit Hook의 videoTarget 옵션을 사용합니다:
import { TapKit, useTapKit } from '@coxwave/tap-kit/react';
import { useRef } from 'react';
function VideoLearningPage() {
const videoRef = useRef<HTMLVideoElement>(null);
const tapkit = useTapKit({
apiKey: 'your-api-key',
userId: 'user-123',
courseId: 'course-456',
clipId: 'clip-789',
videoTarget: videoRef.current,
});
return (
<div className="flex gap-4">
<video ref={videoRef} src="/lecture.mp4" controls className="flex-1" />
<TapKit control={tapkit.control} style={{ height: '600px' }} />
</div>
);
}커스텀 비디오 플레이어 (YouTube, Vimeo 등)
<video> 요소가 아닌 YouTube, Vimeo 등 커스텀 플레이어를 사용하는 경우, VideoPlayerAdapter 인터페이스를 구현합니다.
interface VideoPlayerAdapter {
getCurrentTime: () => number;
setCurrentTime: (time: number, clipId?: string) => void;
}YouTube IFrame API 예시
// YouTube IFrame API 초기화 후
const player = new YT.Player('youtube-player', {
videoId: 'VIDEO_ID',
events: { onReady: onPlayerReady }
});
function onPlayerReady() {
const kit = document.querySelector('tap-kit');
kit.videoTarget = {
getCurrentTime: () => player.getCurrentTime(),
setCurrentTime: (time, clipId) => player.seekTo(time, true),
};
}Vimeo Player 예시
const player = new Vimeo.Player('vimeo-player');
kit.videoTarget = {
getCurrentTime: () => {
// Vimeo는 Promise 반환하므로 동기적으로 캐시된 값 사용
return cachedCurrentTime;
},
setCurrentTime: (time, clipId) => player.setCurrentTime(time),
};
// 재생 시간 캐싱
player.on('timeupdate', (data) => {
cachedCurrentTime = data.seconds;
});clipId 파라미터는 여러 클립 간 이동(cross-clip seeking) 시 활용됩니다. 단일 비디오만 사용하는 경우 무시해도 됩니다.
타임라인 이벤트 처리
채팅에서 사용자가 특정 타임라인을 클릭하면 onTimelineSeek 이벤트가 발생합니다.
const tapkit = useTapKit({
apiKey: 'your-api-key',
userId: 'user-123',
courseId: 'course-456',
clipId: 'clip-789',
videoTarget: videoRef.current,
onTimelineSeek: (time, clipId) => {
// time: 이동할 재생 위치 (초 단위)
// clipId: 해당 클립 ID (cross-clip 시 사용)
if (videoRef.current) {
videoRef.current.currentTime = time;
}
},
});CDN 방식에서는 kit.events.onTimelineSeek()을 사용합니다:
const unsubscribe = kit.events.onTimelineSeek((time, clipId) => {
videoElement.currentTime = time;
});
// 구독 해제
unsubscribe();video.bind() API
videoTarget 속성 외에 kit.video.bind() 메서드로도 동기화할 수 있습니다.
// HTMLVideoElement
kit.video.bind(videoElement);
// 커스텀 어댑터
kit.video.bind({
getCurrentTime: () => player.getCurrentTime(),
setCurrentTime: (time, clipId) => player.seekTo(time),
});
// 바인딩 해제
kit.video.unbind();videoTarget vs video.bind()
| 방식 | 사용 시점 | 특징 |
|---|---|---|
videoTarget | 선언적, 권장 | 라이프사이클 변경에도 유지됨 |
video.bind() | 명령적 | 수동으로 bind/unbind 관리 필요 |
videoTarget은 내부적으로 video.bind()를 호출합니다.
일반적으로 videoTarget 사용을 권장합니다.
강의 변경 시 동기화
setCourse()로 강의를 변경해도 비디오 동기화는 자동으로 유지됩니다.
// 초기 설정
kit.videoTarget = videoElement;
// 나중에 강의 변경 - 비디오 동기화 자동 유지
kit.setCourse({
courseId: 'new-course',
clipId: 'new-clip',
});멀티 클립 환경에서 다른 클립의 타임라인을 클릭하면 onTimelineSeek에 해당 clipId가 전달됩니다:
kit.events.onTimelineSeek((time, clipId) => {
if (clipId !== currentClipId) {
// 다른 클립으로 이동
loadVideo(clipId);
}
videoElement.currentTime = time;
});