Production-ready video recording, upload, playback, and captions library for React. HLS streaming, multiple view modes, VTT/SRT subtitles.
npm install @classytic/react-videoModern video player library for React with composable primitives and pre-built players.
- Composable Primitives - Build custom players with unstyled components
- 4 Pre-built Players - StandardPlayer, ShortsPlayer, CinematicPlayer, ReelPlayer
- HLS Adaptive Streaming - Automatic quality switching
- Recording & Upload - Screen recording with S3/Cloudflare upload
- Captions & Chapters - WebVTT support with thumbnail sprites
- TypeScript - Full type safety
- Modern React - React 18/19 with hooks
``bash`
npm install @classytic/react-video
`tsx
import { StandardPlayer, ShortsPlayer, ReelPlayer } from '@classytic/react-video';
// 16:9 Landscape (YouTube-style)
// 9:16 Vertical (YouTube Shorts)
creator={{ name: '@user', avatar: '/avatar.jpg' }}
likes={1250}
/>
// 9:16 Stories (Instagram/TikTok)
{ id: '1', src: '/video1.mp4' },
{ id: '2', src: '/video2.mp4' },
]}
/>
// 21:9 Cinematic (Netflix-style)
`
`tsx
import { VideoController, VideoRoot, Video, PlayButton, TimeSlider } from '@classytic/react-video';
Import only the provider you need for optimal bundle size: function Recorder() { const recorder = useVideoRecorder({ return ( Uploading: {recorder.progress}% function Recorder() { // ... rest of component Note: String-based imports bundle all providers. Use tree-shakeable imports for smaller bundles. Features: | Key | Action | Full TypeScript support with exported types: Primitives give you full control over styling and layout: // ✅ Full control with Tailwind Pre-built players are reference implementations you can: They're not meant to be "one size fits all" - they're starting points. This library uses a provider pattern for uploads - you choose where to upload: // S3 via presigned URLs (no AWS SDK required) // Cloudflare Stream // Use any provider with VideoRecorder Benefits: Implement the const customProvider: UploadProvider = { async initUpload(params) { async uploadChunk(session, chunk, partNumber) { async completeUpload(session, parts, metadata) { async abortUpload(session) { // Use custom provider const provider = createS3Provider({ const recorder = useVideoRecorder({ const provider = createCloudflareProvider({ const recorder = useVideoRecorder({ See the examples directory for complete examples: - React 18.0.0+ MIT
{(progress) => (
${progress}% }} />
)}
{(state) => state.isPlaying ? 'Pause' : 'Play'}
``HLS Streaming
tsx`
spriteVttUrl="sprites.vtt"
chaptersVttUrl="chapters.vtt"
/>`Recording & Upload
$3
tsx`
import { useVideoRecorder } from '@classytic/react-video';
import { createS3Provider } from '@classytic/react-video/providers/s3';
// Or: import { createCloudflareProvider } from '@classytic/react-video/providers/cloudflare';
const s3Provider = createS3Provider({ apiBase: '/api/upload' });
provider: s3Provider,
quality: 'medium',
});
{recorder.status === 'idle' && (
)}
{recorder.status === 'recording' && (
)}
{recorder.status === 'uploading' &&
);
}`$3
tsx`
import { useVideoRecorder } from '@classytic/react-video';
const recorder = useVideoRecorder({
provider: 's3', // String: 's3' or 'cloudflare'
providerConfig: { apiBase: '/api/upload' },
quality: 'medium',
});
}VideoControllerAvailable Primitives
$3
- - Context provider with state managementuseVideo()
- - Hook to access video state and actionsVideoRoot
- - Container wrapperVideo
- - Video elementPlayButton$3
- - Play/pause toggleMuteButton
- - Mute/unmute toggleFullscreenButton
- - Fullscreen toggleTimeSlider
- - Progress bar with seekTimeDisplay
- - Current/total timeVolumeSlider
- - Volume controlQualitySelect
- - Quality switcherPlaybackSpeed
- - Playback rate controlThumbnailPreview$3
- - Sprite-based scrubbing previewChaptersMenu
- - Chapter navigationCaptions
- - Subtitle displayDoubleTapSeek
- - Mobile-style seek gesturesPlayPauseAnimation
- - Visual feedback`Pre-built Players
$3
16:9 landscape player with bottom controls, hover to show.tsx`
hlsSrc="video.m3u8"
poster="poster.jpg"
spriteVttUrl="sprites.vtt"
/>`$3
9:16 vertical player with YouTube Shorts UI (sidebar actions).tsx`
creator={{ name: '@user', avatar: '/avatar.jpg' }}
description="Check this out!"
likes={1250}
comments={45}
onLike={() => {}}
onComment={() => {}}
onShare={() => {}}
/>`$3
9:16 vertical player with Instagram/TikTok stories UI (progress bars, smooth transitions).tsx`
{ id: '1', src: '/video1.mp4', poster: '/poster1.jpg' },
{ id: '2', src: '/video2.mp4', poster: '/poster2.jpg' },
{ id: '3', src: '/video3.mp4', poster: '/poster3.jpg' },
]}
autoPlay
autoAdvance
smoothTransitions
/>`
- Tap left/right edges to navigate
- Tap center to play/pause
- Story progress bars at top
- Smooth crossfade transitions
- Next video preloading$3
21:9 ultra-wide player with Netflix-style UI.tsx`
title="Movie Title"
autoPlay={false}
/>`Keyboard Shortcuts
|-----|--------|
| Space / K | Play/Pause |
| ← / J | Seek backward 10s |
| → / L | Seek forward 10s |
| ↑ | Volume up |
| ↓ | Volume down |
| M | Toggle mute |
| F | Toggle fullscreen |
| 0-9 | Jump to % of video |TypeScript
tsx`
import type {
VideoState,
VideoActions,
QualityLevel,
TextTrack,
Chapter
} from '@classytic/react-video';`Architecture
$3
tsx`
// ❌ Limited customization
{(state) => state.isPlaying ? '⏸' : '▶'}
`$3
1. Use directly for quick prototypes
2. Copy and customize for your design system
3. Study as examples of how to compose primitivesUpload Providers (Advanced)
$3
typescript`
import { createS3Provider, createCloudflareProvider } from '@classytic/react-video';
const s3 = createS3Provider({ apiBase: '/api/upload' });
const cloudflare = createCloudflareProvider({ apiBase: '/api/upload' });
const recorder = new VideoRecorder(s3, { id: 'recording-123' });UploadProvider
- ✅ No vendor lock-in - Switch providers without changing code
- ✅ Small bundle - No cloud SDKs bundled (uses fetch)
- ✅ Testable - Mock providers easily
- ✅ Extensible - Implement custom providers$3
interface for custom backends:`typescript`
import type { UploadProvider } from '@classytic/react-video';
name: 'custom',
const res = await fetch('/my-api/init-upload', {
method: 'POST',
body: JSON.stringify(params),
});
return res.json();
},
// Upload chunk to your backend
return { partNumber, size: chunk.size };
},
// Finalize upload
return { success: true, url: '...' };
},
// Cancel upload
},
};
const recorder = new VideoRecorder(customProvider, { id: 'rec-123' });`Advanced Usage
$3
tsx
${progress}% }} />
)}
className="absolute -top-32"
/>
``$3
tsx`
{ id: 'en', src: '/en.vtt', label: 'English', default: true },
{ id: 'es', src: '/es.vtt', label: 'Español' },
]}
style={{
fontSize: 20,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: '#ffffff',
}}
/>`$3
tsx`
{({ qualities, currentQuality, setQuality }) => (
value={currentQuality}
onChange={(e) => setQuality(Number(e.target.value))}
className="bg-black/80 text-white px-3 py-2 rounded"
>
{qualities.map((q, i) => (
))}
)}`Provider Setup
$3
tsx`
import { createS3Provider } from '@classytic/react-video';
apiBase: '/api/upload',
// Your backend handles S3 presigned URLs
});
provider: 's3',
providerConfig: { apiBase: '/api/upload' }
});`$3
tsx`
import { createCloudflareProvider } from '@classytic/react-video';
apiBase: '/api/upload',
// Your backend handles Cloudflare Stream Direct Creator Upload
});
provider: 'cloudflare',
providerConfig: { apiBase: '/api/upload' }
});complete-player.tsxExamples
- - Full-featured player with all controlscustom-ui-with-callbacks.tsx
- - Custom UI with event callbacks@classytic/hls-processor` - HLS video processor with thumbnails & chaptersRequirements
- Node.js 18.0.0+
- Modern browsers with ES2020 supportLicense
Related Packages