edutap.ai developers
SDK Integration

Video Integration

Synchronizing TapKit with video players

Video Integration

Integrate the AI tutor to recognize the current lecture playback position and automatically seek the video when clicking specific segments in the chat.

Why video integration is recommended

  • AI provides more accurate answers based on current playback position
  • Auto-seek when clicking specific segments in chat
  • Automatic new clip tracking when changing courses (setCourse)

Basic Usage (HTMLVideoElement)

The simplest method is passing a <video> element to the videoTarget property.

<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>

In React, use the videoTarget option in the useTapKit Hook:

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

Custom Video Players (YouTube, Vimeo, etc.)

For custom players like YouTube or Vimeo instead of <video> elements, implement the VideoPlayerAdapter interface.

interface VideoPlayerAdapter {
  getCurrentTime: () => number;
  setCurrentTime: (time: number, clipId?: string) => void;
}

YouTube IFrame API Example

// After initializing 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 Example

const player = new Vimeo.Player('vimeo-player');

kit.videoTarget = {
  getCurrentTime: () => {
    // Vimeo returns Promise, so use cached value synchronously
    return cachedCurrentTime;
  },
  setCurrentTime: (time, clipId) => player.setCurrentTime(time),
};

// Cache playback time
player.on('timeupdate', (data) => {
  cachedCurrentTime = data.seconds;
});

The clipId parameter is used for cross-clip seeking. It can be ignored when using a single video.


Timeline Event Handling

When users click a specific timeline in the chat, the onTimelineSeek event is fired.

const tapkit = useTapKit({
  apiKey: 'your-api-key',
  userId: 'user-123',
  courseId: 'course-456',
  clipId: 'clip-789',
  videoTarget: videoRef.current,
  onTimelineSeek: (time, clipId) => {
    // time: playback position to seek (seconds)
    // clipId: the clip ID (used for cross-clip)
    if (videoRef.current) {
      videoRef.current.currentTime = time;
    }
  },
});

For CDN usage, use kit.events.onTimelineSeek():

const unsubscribe = kit.events.onTimelineSeek((time, clipId) => {
  videoElement.currentTime = time;
});

// Unsubscribe
unsubscribe();

video.bind() API

Besides the videoTarget property, you can also synchronize using the kit.video.bind() method.

// HTMLVideoElement
kit.video.bind(videoElement);

// Custom adapter
kit.video.bind({
  getCurrentTime: () => player.getCurrentTime(),
  setCurrentTime: (time, clipId) => player.seekTo(time),
});

// Unbind
kit.video.unbind();

videoTarget vs video.bind()

MethodWhen to UseCharacteristics
videoTargetDeclarative, recommendedPersists through lifecycle changes
video.bind()ImperativeRequires manual bind/unbind management

videoTarget internally calls video.bind(). Using videoTarget is generally recommended.


Syncing When Changing Courses

Video synchronization is automatically maintained even when changing courses with setCourse().

// Initial setup
kit.videoTarget = videoElement;

// When changing courses later - video sync automatically maintained
kit.setCourse({
  courseId: 'new-course',
  clipId: 'new-clip',
});

In multi-clip environments, when clicking a timeline from a different clip, the clipId is passed to onTimelineSeek:

kit.events.onTimelineSeek((time, clipId) => {
  if (clipId !== currentClipId) {
    // Navigate to different clip
    loadVideo(clipId);
  }
  videoElement.currentTime = time;
});

Next Steps