@classytic/react-video
v0.1.0
Published
Production-ready video recording, upload, playback, and captions library for React. HLS streaming, multiple view modes, VTT/SRT subtitles.
Downloads
107
Maintainers
Readme
@classytic/react-video
Modern video player library for React with composable primitives and pre-built players.
Features
- 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
Installation
npm install @classytic/react-videoQuick Start
Option 1: Pre-built Players (Easiest)
import { StandardPlayer, ShortsPlayer, ReelPlayer } from '@classytic/react-video';
// 16:9 Landscape (YouTube-style)
<StandardPlayer src="video.mp4" />
// 9:16 Vertical (YouTube Shorts)
<ShortsPlayer
src="short.mp4"
creator={{ name: '@user', avatar: '/avatar.jpg' }}
likes={1250}
/>
// 9:16 Stories (Instagram/TikTok)
<ReelPlayer
items={[
{ id: '1', src: '/video1.mp4' },
{ id: '2', src: '/video2.mp4' },
]}
/>
// 21:9 Cinematic (Netflix-style)
<CinematicPlayer src="movie.mp4" title="Movie Title" />Option 2: Build Custom with Primitives
import { VideoController, VideoRoot, Video, PlayButton, TimeSlider } from '@classytic/react-video';
<VideoController src="video.mp4">
<VideoRoot className="w-full aspect-video bg-black relative">
<Video className="w-full h-full object-contain" />
<div className="absolute bottom-4 left-4 right-4 space-y-2">
<TimeSlider className="h-1 bg-white/20 rounded cursor-pointer">
{(progress) => (
<div className="h-full bg-white rounded" style={{ width: `${progress}%` }} />
)}
</TimeSlider>
<PlayButton className="px-4 py-2 bg-white/90 rounded text-black">
{(state) => state.isPlaying ? 'Pause' : 'Play'}
</PlayButton>
</div>
</VideoRoot>
</VideoController>HLS Streaming
<StandardPlayer
hlsSrc="video.m3u8"
spriteVttUrl="sprites.vtt"
chaptersVttUrl="chapters.vtt"
/>Recording & Upload
Option 1: Tree-shakeable Provider Imports (Recommended)
Import only the provider you need for optimal bundle size:
import { useVideoRecorder } from '@classytic/react-video';
import { createS3Provider } from '@classytic/react-video/providers/s3';
// Or: import { createCloudflareProvider } from '@classytic/react-video/providers/cloudflare';
function Recorder() {
const s3Provider = createS3Provider({ apiBase: '/api/upload' });
const recorder = useVideoRecorder({
provider: s3Provider,
quality: 'medium',
});
return (
<div>
{recorder.status === 'idle' && (
<button onClick={() => recorder.start({ id: 'session-1' })}>
Start Recording
</button>
)}
{recorder.status === 'recording' && (
<button onClick={recorder.stop}>Stop & Upload</button>
)}
{recorder.status === 'uploading' && <p>Uploading: {recorder.progress}%</p>}
</div>
);
}Option 2: String-based Provider (Simple)
import { useVideoRecorder } from '@classytic/react-video';
function Recorder() {
const recorder = useVideoRecorder({
provider: 's3', // String: 's3' or 'cloudflare'
providerConfig: { apiBase: '/api/upload' },
quality: 'medium',
});
// ... rest of component
}Note: String-based imports bundle all providers. Use tree-shakeable imports for smaller bundles.
Available Primitives
Core
VideoController- Context provider with state managementuseVideo()- Hook to access video state and actionsVideoRoot- Container wrapperVideo- Video element
Controls
PlayButton- Play/pause toggleMuteButton- Mute/unmute toggleFullscreenButton- Fullscreen toggleTimeSlider- Progress bar with seekTimeDisplay- Current/total timeVolumeSlider- Volume controlQualitySelect- Quality switcherPlaybackSpeed- Playback rate control
Advanced
ThumbnailPreview- Sprite-based scrubbing previewChaptersMenu- Chapter navigationCaptions- Subtitle displayDoubleTapSeek- Mobile-style seek gesturesPlayPauseAnimation- Visual feedback
Pre-built Players
StandardPlayer
16:9 landscape player with bottom controls, hover to show.
<StandardPlayer
src="video.mp4"
hlsSrc="video.m3u8"
poster="poster.jpg"
spriteVttUrl="sprites.vtt"
/>ShortsPlayer
9:16 vertical player with YouTube Shorts UI (sidebar actions).
<ShortsPlayer
src="short.mp4"
creator={{ name: '@user', avatar: '/avatar.jpg' }}
description="Check this out!"
likes={1250}
comments={45}
onLike={() => {}}
onComment={() => {}}
onShare={() => {}}
/>ReelPlayer
9:16 vertical player with Instagram/TikTok stories UI (progress bars, smooth transitions).
<ReelPlayer
items={[
{ 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
/>Features:
- Tap left/right edges to navigate
- Tap center to play/pause
- Story progress bars at top
- Smooth crossfade transitions
- Next video preloading
CinematicPlayer
21:9 ultra-wide player with Netflix-style UI.
<CinematicPlayer
src="movie.mp4"
title="Movie Title"
autoPlay={false}
/>Keyboard Shortcuts
| Key | Action | |-----|--------| | 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
Full TypeScript support with exported types:
import type {
VideoState,
VideoActions,
QualityLevel,
TextTrack,
Chapter
} from '@classytic/react-video';Architecture
Why Primitives?
Primitives give you full control over styling and layout:
// ❌ Limited customization
<VideoPlayer theme="dark" controls={true} />
// ✅ Full control with Tailwind
<VideoController src="video.mp4">
<VideoRoot className="rounded-xl overflow-hidden shadow-2xl">
<Video className="object-cover" />
<PlayButton className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-20 h-20 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full">
{(state) => state.isPlaying ? '⏸' : '▶'}
</PlayButton>
</VideoRoot>
</VideoController>Pre-built Players
Pre-built players are reference implementations you can:
- Use directly for quick prototypes
- Copy and customize for your design system
- Study as examples of how to compose primitives
They're not meant to be "one size fits all" - they're starting points.
Upload Providers (Advanced)
Provider Pattern
This library uses a provider pattern for uploads - you choose where to upload:
import { createS3Provider, createCloudflareProvider } from '@classytic/react-video';
// S3 via presigned URLs (no AWS SDK required)
const s3 = createS3Provider({ apiBase: '/api/upload' });
// Cloudflare Stream
const cloudflare = createCloudflareProvider({ apiBase: '/api/upload' });
// Use any provider with VideoRecorder
const recorder = new VideoRecorder(s3, { id: 'recording-123' });Benefits:
- ✅ 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
Custom Upload Provider
Implement the UploadProvider interface for custom backends:
import type { UploadProvider } from '@classytic/react-video';
const customProvider: UploadProvider = {
name: 'custom',
async initUpload(params) {
const res = await fetch('/my-api/init-upload', {
method: 'POST',
body: JSON.stringify(params),
});
return res.json();
},
async uploadChunk(session, chunk, partNumber) {
// Upload chunk to your backend
return { partNumber, size: chunk.size };
},
async completeUpload(session, parts, metadata) {
// Finalize upload
return { success: true, url: '...' };
},
async abortUpload(session) {
// Cancel upload
},
};
// Use custom provider
const recorder = new VideoRecorder(customProvider, { id: 'rec-123' });Advanced Usage
Custom Progress Bar with Thumbnail Preview
<VideoController src="video.mp4">
<VideoRoot className="relative w-full aspect-video">
<Video />
<div className="relative">
<TimeSlider>{(progress) => (
<div className="h-1 bg-white/20">
<div className="h-full bg-red-600" style={{ width: `${progress}%` }} />
</div>
)}</TimeSlider>
<ThumbnailPreview
spriteVttUrl="sprites.vtt"
className="absolute -top-32"
/>
</div>
</VideoRoot>
</VideoController>Multiple Caption Tracks
<Captions
tracks={[
{ 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',
}}
/>Custom Quality Selector
<QualitySelect>
{({ qualities, currentQuality, setQuality }) => (
<select
value={currentQuality}
onChange={(e) => setQuality(Number(e.target.value))}
className="bg-black/80 text-white px-3 py-2 rounded"
>
{qualities.map((q, i) => (
<option key={i} value={i}>{q.label}</option>
))}
</select>
)}
</QualitySelect>Provider Setup
S3 Upload
import { createS3Provider } from '@classytic/react-video';
const provider = createS3Provider({
apiBase: '/api/upload',
// Your backend handles S3 presigned URLs
});
const recorder = useVideoRecorder({
provider: 's3',
providerConfig: { apiBase: '/api/upload' }
});Cloudflare Stream
import { createCloudflareProvider } from '@classytic/react-video';
const provider = createCloudflareProvider({
apiBase: '/api/upload',
// Your backend handles Cloudflare Stream Direct Creator Upload
});
const recorder = useVideoRecorder({
provider: 'cloudflare',
providerConfig: { apiBase: '/api/upload' }
});Examples
See the examples directory for complete examples:
complete-player.tsx- Full-featured player with all controlscustom-ui-with-callbacks.tsx- Custom UI with event callbacks
Requirements
- React 18.0.0+
- Node.js 18.0.0+
- Modern browsers with ES2020 support
License
MIT
Related Packages
@classytic/hls-processor- HLS video processor with thumbnails & chapters
