edutap.ai developers

비디오 연동

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;
});

다음 단계