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()
| Method | When to Use | Characteristics |
|---|---|---|
videoTarget | Declarative, recommended | Persists through lifecycle changes |
video.bind() | Imperative | Requires 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;
});