Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Zi
Vidtreo SDK for browser-based video recording and transcoding. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications. Features include camera and screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads.
``bash`
npm install @vidtreo/recorder
`typescript
import { VidtreoRecorder } from '@vidtreo/recorder';
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://core.vidtreo.com', // Optional, defaults to https://core.vidtreo.com
enableSourceSwitching: true,
maxRecordingTime: 300000,
onUploadComplete: (result) => {
console.log('Recording ID:', result.recordingId);
console.log('Video URL:', result.uploadUrl);
},
});
await recorder.startPreview('camera');
await recorder.startRecording({}, 'camera');
const result = await recorder.stopRecording();
recorder.cleanup();
`
The main class for recording video from camera or screen sources.
#### Constructor
`typescript`
new VidtreoRecorder(config: VidtreoRecorderConfig)
Creates a new recorder instance with the specified configuration.
Parameters:
- config.apiKey (required): Your API key for backend authenticationconfig.apiUrl
- (optional): Your backend API URL endpoint. Defaults to https://core.vidtreo.com if not providedconfig.enableSourceSwitching
- (optional): Enable switching between camera and screen during recording. Default: falseconfig.enableMute
- (optional): Enable mute/unmute functionality. When disabled, muteAudio(), unmuteAudio(), and toggleMute() will throw errors. Default: trueconfig.enablePause
- (optional): Enable pause/resume functionality. When disabled, pauseRecording() and resumeRecording() will throw errors. Default: trueconfig.enableDeviceChange
- (optional): Enable device selection functionality. When disabled, getAvailableDevices() will throw an error. Default: trueconfig.maxRecordingTime
- (optional): Maximum recording duration in milliseconds. When the maximum time is reached, recording automatically stops. If not set, recording can continue indefinitely until manually stopped. Examples: 300000 (5 minutes), 600000 (10 minutes), 1800000 (30 minutes)config.countdownDuration
- (optional): Countdown duration in milliseconds before recording starts. Default: 0 (no countdown)config.userMetadata
- (optional): Custom metadata object to include with uploadsconfig.onUploadComplete
- (optional): Callback invoked when upload completes successfullyconfig.onUploadProgress
- (optional): Callback invoked during upload with progress value (0-1)config.onUploadError
- (optional): Callback invoked when upload failsconfig.onRecordingStart
- (optional): Callback invoked when recording startsconfig.onRecordingStop
- (optional): Callback invoked when recording stopsconfig.onError
- (optional): Callback invoked when a stream error occurs
Throws: Error if apiKey is missing
#### Methods
##### initialize(): Promise
Initializes the recorder with the provided configuration. Called automatically when needed, but can be called explicitly for early initialization.
Returns: Promise
##### startPreview(sourceType?: SourceType): Promise
Starts a preview stream from the specified source without recording.
Parameters:
- sourceType (optional): Source type to preview. Either 'camera' or 'screen'. Default: 'camera'
Returns: Promise - The preview media stream
Throws: Error if stream initialization fails
##### startRecording(options?: RecordingStartOptions, sourceType?: SourceType): Promise
Starts recording from the specified source. If no preview is active, automatically starts one.
Parameters:
- options (optional): Recording options objectoptions.video
- : Video constraints (boolean or CameraConstraints object)options.audio
- : Audio constraints (boolean or MediaTrackConstraints object)sourceType
- (optional): Source type to record from. Either 'camera' or 'screen'. Default: 'camera'
Returns: Promise
Throws: Error if recording cannot be started
##### stopRecording(): Promise
Stops the current recording, transcodes the video, and uploads it to the backend.
Returns: Promise - Object containing:recordingId
- : Unique identifier for the uploaded recordinguploadUrl
- : URL where the video can be accessedblob
- : The recorded video as a Blob
Throws: Error if recording is not active or upload fails
##### switchSource(sourceType: SourceType): Promise
Switches the recording source between camera and screen during an active recording.
Parameters:
- sourceType: Target source type. Either 'camera' or 'screen'
Returns: Promise
Throws: Error if source switching is not enabled or if not currently recording
##### getAvailableDevices(): Promise
Retrieves the list of available camera and microphone devices.
Returns: Promise - Object containing:video
- : Array of available video input devicesaudio
- : Array of available audio input devices
Throws: Error if device change functionality is disabled (enableDeviceChange === false)
##### muteAudio(): void
Mutes the audio track during recording.
Throws: Error if mute functionality is disabled (enableMute === false)
##### unmuteAudio(): void
Unmutes the audio track during recording.
Throws: Error if mute functionality is disabled (enableMute === false)
##### toggleMute(): void
Toggles the mute state of the audio track.
Throws: Error if mute functionality is disabled (enableMute === false)
##### isMuted(): boolean
Returns the current mute state.
Returns: true if audio is muted, false otherwise
##### pauseRecording(): void
Pauses the current recording. Video frames are not captured while paused.
Throws: Error if pause functionality is disabled (enablePause === false)
##### resumeRecording(): void
Resumes a paused recording.
Throws: Error if pause functionality is disabled (enablePause === false)
##### isPaused(): boolean
Returns the current pause state.
Returns: true if recording is paused, false otherwise
##### getRecordingState(): RecordingState
Returns the current recording state.
Returns: One of:
- 'idle': Not recording'countdown'
- : Countdown in progress before recording'recording'
- : Actively recording
##### getStream(): MediaStream | null
Returns the current media stream, or null if no stream is active.
Returns: MediaStream | null
##### cleanup(): void
Cleans up all resources, stops active streams, and cancels any pending operations. Should be called when the recorder is no longer needed.
`typescript`
interface VidtreoRecorderConfig {
apiKey: string;
apiUrl?: string;
enableSourceSwitching?: boolean;
enableMute?: boolean;
enablePause?: boolean;
enableDeviceChange?: boolean;
maxRecordingTime?: number;
countdownDuration?: number;
userMetadata?: Record
onUploadComplete?: (result: {
recordingId: string;
uploadUrl: string;
}) => void;
onUploadProgress?: (progress: number) => void;
onUploadError?: (error: Error) => void;
onRecordingStart?: () => void;
onRecordingStop?: () => void;
onError?: (error: Error) => void;
}
Note: For web component usage, see the @vidtreo/recorder-wc package. Use the max-recording-time attribute instead of maxRecordingTime. The attribute accepts a numeric value in milliseconds as a string (e.g., max-recording-time="300000" for 5 minutes).
`typescript`
interface RecordingStartOptions {
video?: boolean | CameraConstraints;
audio?: boolean | MediaTrackConstraints;
}
`typescript`
interface RecordingStopResult {
recordingId: string;
uploadUrl: string;
blob: Blob;
}
`typescript`
type SourceType = 'camera' | 'screen';
`typescript`
type RecordingState = 'idle' | 'countdown' | 'recording';
`typescript`
interface AvailableDevices {
video: MediaDeviceInfo[];
audio: MediaDeviceInfo[];
}
`typescript`
interface CameraConstraints {
width?: number | { ideal?: number; min?: number; max?: number };
height?: number | { ideal?: number; min?: number; max?: number };
frameRate?: number | { ideal?: number; min?: number; max?: number };
}
`typescript
import { VidtreoRecorder } from '@vidtreo/recorder';
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com', // Optional, defaults to https://core.vidtreo.com
onUploadComplete: (result) => {
console.log('Uploaded:', result.uploadUrl);
},
});
try {
await recorder.startPreview('camera');
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
const result = await recorder.stopRecording();
console.log('Recording ID:', result.recordingId);
recorder.cleanup();
}, 10000);
} catch (error) {
console.error('Recording failed:', error);
recorder.cleanup();
}
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
maxRecordingTime: 300000,
onRecordingStop: () => {
console.log('Recording stopped automatically after 5 minutes');
},
});
await recorder.startRecording({}, 'camera');
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
enableSourceSwitching: true,
maxRecordingTime: 600000,
});
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
await recorder.switchSource('screen');
}, 5000);
setTimeout(async () => {
await recorder.switchSource('camera');
}, 10000);
setTimeout(async () => {
const result = await recorder.stopRecording();
recorder.cleanup();
}, 15000);
`
Note: When maxRecordingTime is set, the recording will automatically stop when the time limit is reached, even if the timeout callbacks are still pending. The onRecordingStop callback will be invoked when the maximum time is reached.
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
});
await recorder.startRecording({}, 'camera');
recorder.muteAudio();
setTimeout(() => recorder.unmuteAudio(), 5000);
setTimeout(() => recorder.toggleMute(), 10000);
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 15000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
});
await recorder.startRecording({}, 'camera');
setTimeout(() => recorder.pauseRecording(), 3000);
setTimeout(() => recorder.resumeRecording(), 6000);
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 10000);
`
`typescriptUpload: ${percentage}%
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
onUploadProgress: (progress) => {
const percentage = Math.round(progress * 100);
console.log();
},
onUploadComplete: (result) => {
console.log('Upload complete:', result.uploadUrl);
},
onUploadError: (error) => {
console.error('Upload failed:', error.message);
},
});
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 5000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
userMetadata: {
userId: 'user-123',
sessionId: 'session-456',
projectId: 'project-789',
},
});
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 5000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
countdownDuration: 3000,
});
await recorder.startPreview('camera');
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 10000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
});
const devices = await recorder.getAvailableDevices();
console.log('Cameras:', devices.video.map(d => d.label));
console.log('Microphones:', devices.audio.map(d => d.label));
await recorder.startRecording({}, 'camera');
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 5000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
onRecordingStart: () => {
console.log('Recording started');
},
onRecordingStop: () => {
console.log('Recording stopped');
},
});
await recorder.startRecording({}, 'camera');
const checkState = setInterval(() => {
const state = recorder.getRecordingState();
const isPaused = recorder.isPaused();
const isMuted = recorder.isMuted();
console.log(State: ${state}, Paused: ${isPaused}, Muted: ${isMuted});
}, 1000);
setTimeout(async () => {
clearInterval(checkState);
await recorder.stopRecording();
recorder.cleanup();
}, 10000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
enableSourceSwitching: true,
});
await recorder.startPreview('screen');
await recorder.startRecording({}, 'screen');
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 30000);
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
onError: (error) => {
console.error('Stream error:', error.message);
},
onUploadError: (error) => {
console.error('Upload error:', error.message);
},
});
try {
await recorder.startRecording({}, 'camera');
const result = await recorder.stopRecording();
console.log('Success:', result.uploadUrl);
} catch (error) {
console.error('Recording error:', error);
} finally {
recorder.cleanup();
}
`
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com',
enableMute: false,
enablePause: false,
enableSourceSwitching: false,
enableDeviceChange: false,
});
await recorder.startRecording({}, 'camera');
try {
recorder.muteAudio();
} catch (error) {
console.error('Mute is disabled:', error.message);
}
try {
recorder.pauseRecording();
} catch (error) {
console.error('Pause is disabled:', error.message);
}
setTimeout(async () => {
await recorder.stopRecording();
recorder.cleanup();
}, 10000);
`
The web component is available in a separate package: @vidtreo/recorder-wc
For web component installation, usage, and examples, please refer to the @vidtreo/recorder-wc documentation.
The recorder automatically sends telemetry events to monitor SDK usage, detect issues, and improve the product. Telemetry is always active and cannot be disabled through configuration.
#### When Telemetry is Activated
Telemetry is initialized when you call initialize() on the recorder instance:
`typescript
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://api.example.com', // This URL is also used for telemetry
});
await recorder.initialize(); // Telemetry client is initialized here
`
#### Telemetry Endpoint
Telemetry events are sent to:
- Default: {apiUrl}/api/v1/telemetryapiUrl
- Same used for config and upload operations
The apiUrl can be overridden when creating the recorder:
`typescript`
const recorder = new VidtreoRecorder({
apiKey: 'your-api-key',
apiUrl: 'https://custom-api.example.com', // Telemetry, config, and upload all use this URL
});
#### Delivery Behavior
Telemetry delivery is optimized for low overhead and no duplicate events:
- Events are batched in memory and flushed after 10 events or 1 second
- One-time events are deduped per session (sdk.init.started, sdk.init.succeeded, sdk.init.failed)stream.error
- Noisy errors are throttled (currently emits at most once per 5 seconds)
#### Events Tracked
The SDK automatically tracks the following telemetry events:
| Event Name | Category | Description |
|------------|----------|-------------|
| Lifecycle Events | | |
| sdk.init.started | lifecycle | Sent when recorder initialization begins |sdk.init.succeeded
| | lifecycle | Sent when recorder initialization completes successfully |sdk.init.failed
| | error | Sent when recorder initialization fails (includes error details) |preview.start.succeeded
| Preview Events | | |
| | interaction | Sent when preview stream starts successfully |preview.start.failed
| | error | Sent when preview stream fails to start (includes error details) |recording.start.requested
| Recording Events | | |
| | interaction | Sent when user requests to start recording |recording.start.succeeded
| | interaction | Sent when recording starts successfully |recording.start.failed
| | error | Sent when recording fails to start (includes error details) |recording.stop.requested
| | interaction | Sent when user requests to stop recording |recording.stop.succeeded
| | interaction | Sent when recording stops successfully |recording.stop.failed
| | error | Sent when recording fails to stop (includes error details) |upload.started
| Upload Events | | |
| | performance | Sent when video upload begins |upload.succeeded
| | performance | Sent when video upload completes successfully |upload.failed
| | error | Sent when video upload fails (includes error details) |source.switch.requested
| Source Switch Events | | |
| | interaction | Sent when source switch is requested |source.switch.succeeded
| | interaction | Sent when source switch completes successfully |source.switch.failed
| | error | Sent when source switch fails (includes error details) |stream.error
| Stream Events | | |
| | error | Sent when a stream error occurs (includes error details) |
#### Event Categories
Events are categorized for analysis:
- lifecycle: SDK initialization events
- interaction: User-initiated actions (start/stop/switch)
- performance: Upload performance metrics
- error: Any error that occurs during operations
#### Event Properties
Events include the following properties automatically:
- browserName: Detected browser (chrome, firefox, safari, edge, unknown)
- sourceType: Source being used (camera or screen)
- filename: Name of uploaded file (upload events)
- duration: Video duration in milliseconds (upload events)
- recordingId: ID of uploaded recording (upload.succeeded)
#### Error Details
Error events include additional information:
- error.message: Human-readable error message
- error.name: Error type/name
- error.stack: Stack trace (if available)
#### Telemetry Payload
Each telemetry request includes:
`typescript`
{
events: [
{
event: string, // Event name
category: string, // lifecycle | interaction | performance | error
timestamp: number, // Unix timestamp in milliseconds
installationId: string, // Persistent per-browser installation ID
sdkVersion: string, // SDK version from package.json
fingerprint: {
userAgent?: string,
language?: string,
platform?: string,
hardwareConcurrency?: number,
deviceMemory?: number,
},
context?: {
sessionId?: string,
userId?: string,
environmentId?: string,
appVersion?: string,
release?: string,
pageUrl?: string,
referrerUrl?: string,
sdkLocation?: string,
clientLocation?: string,
},
properties?: Record
error?: {
message: string,
name?: string,
stack?: string,
},
},
];
}
Note: The API key is only sent in the Authorization header for other requests (like upload). It is not included in telemetry payloads.
#### Installation ID
A unique installationId is generated per browser and persisted in localStorage under key VIDTREO_INSTALLATION_ID. This ID persists across sessions and helps identify unique installations.
#### Privacy
- API keys are sent in the Authorization header as Bearer ${apiKey} (not hashed)userMetadata
- Installation IDs are anonymous and randomly generated
- User and session identifiers are optional and provided by your application
- No personally identifiable information is collected unless explicitly provided through
For advanced use cases, the package exports lower-level APIs that provide more granular control:
#### Transcoding
`typescript
import { transcodeVideo, DEFAULT_TRANSCODE_CONFIG } from '@vidtreo/recorder';
const videoBlob = new Blob([videoData], { type: 'video/mp4' });
const result = await transcodeVideo(videoBlob, {
width: 1920,
height: 1080,
fps: 60,
bitrate: 2000000,
});
console.log('Transcoded size:', result.buffer.byteLength);
`
#### RecorderController
For fine-grained control over the recording process:
`typescript
import { RecorderController } from '@vidtreo/recorder';
const controller = new RecorderController({
recording: {
onStateChange: (state) => console.log('State:', state),
},
});
await controller.initialize({
apiKey: 'your-api-key',
});
`
#### Stream Management
`typescript
import { CameraStreamManager } from '@vidtreo/recorder';
const streamManager = new CameraStreamManager();
await streamManager.startStream();
const stream = streamManager.getStream();
`
Video transcoding configuration is managed through your backend API. The recorder fetches configuration using the provided apiKey and apiUrl.
The recorder supports real-time watermark rendering. This is highly optimized for performance:
- One-time preparation: The watermark is loaded and pre-rendered once before recording starts.
- Dynamic Scaling: The watermark automatically scales to 7% of the video width while maintaining its aspect ratio.
- Efficient Composition: The watermark is drawn directly onto a composition canvas, avoiding redundant opacity calculations per frame.
Watermark Configuration:
- url: The URL of the image (PNG, JPG, or SVG). Data URLs (base64) are also supported and recommended for reliability.opacity
- : Watermark opacity (0.0 to 1.0). Default: 1.0 (Recommended for best video compression).position
- : One of "top-left", "top-right", "bottom-left", "bottom-right", or "center". Default: "bottom-right".
Default transcoding settings include:
- Format: MP4
- Frame rate: 30 fps
- Resolution: 1280x720
- Bitrate: 500 kbps
- Audio codec: Opus
- Preset: Medium quality
These defaults are used when backend configuration is unavailable or during initialization.
This package requires modern browser APIs for full functionality. The most critical requirement is support for the WebCodecs API, which enables real-time MP4 transcoding.
The following browsers support all features including real-time MP4 transcoding:
- Chrome 94+ - Full support for all features
- Edge (Chromium) 94+ - Full support, same as Chrome
- Opera 80+ - Full support, Chromium-based
- Brave 1.30+ - Full support, Chromium-based
- Vivaldi 5.0+ - Full support, Chromium-based
- Firefox 130+ - Full WebCodecs support
- Safari (macOS/iOS) 26.0+ - Full WebCodecs support
The following browsers support core recording features but may have limitations:
- Firefox 76-129 - AudioWorklet supported, but WebCodecs not available. Real-time MP4 transcoding unavailable; falls back to MediaRecorder (WebM format)
- Safari (macOS/iOS) 16.4-25.x - Partial WebCodecs support; some codecs may not be available
- WebCodecs API - Required for real-time MP4 transcoding (via mediabunny)
- Web Workers - Required for video processing (no fallback available)
- MediaStreamTrackProcessor - Required for track processing in workers
- VideoFrame - Required for video frame processing in workers
- AudioData - Required for audio processing in workers
- Screen Capture API (getDisplayMedia) - Required for screen recordinggetUserMedia`) - Required for camera/microphone access
- MediaDevices API (
- IndexedDB - Required for persistent upload queue
- Storage API - Required for storage quota management
Note: This package requires modern browsers with full Web Worker support. There is no fallback for browsers without Web Workers, MediaStreamTrackProcessor, VideoFrame, or AudioData APIs.
| Feature | Chrome 94+ | Edge 94+ | Firefox 130+ | Safari 26.0+ | Firefox 76-129 | Safari 16.4-25.x |
|---------|------------|---------|--------------|--------------|---------------|------------------|
| Camera Recording | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Screen Recording | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Real-time MP4 Transcoding | ✅ | ✅ | ✅ | ✅ | ❌ (WebM) | ⚠️ (Partial) |
| Audio Level Analysis | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Source Switching | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Device Switching | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Persistent Upload Queue | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- Internet Explorer (all versions)
- Firefox < 76 (missing AudioWorklet)
- Safari < 14.1 (missing AudioWorklet)
- Safari < 16.4 (missing OffscreenCanvas and WebCodecs)
- Edge Legacy (pre-Chromium)
- Chrome < 94 (missing WebCodecs)
- Chrome Android 94+ - Full support
- Samsung Internet 18.0+ - Full support
- Firefox Android 130+ - Full support
- Safari iOS 16.4+ - Partial support (WebCodecs partial)
- Safari iOS 26.0+ - Full support
1. HTTPS Required: Media capture APIs require HTTPS (or localhost) in most browsers
2. User Permissions: Camera, microphone, and screen capture require explicit user permission
3. Web Workers Required: This package requires Web Workers for video processing. Browsers without Web Worker support (or missing MediaStreamTrackProcessor, VideoFrame, AudioData APIs) are not supported
4. No Fallback: The package does not include a fallback for browsers without Web Worker support. Ensure your target browsers meet the minimum requirements
For detailed browser compatibility information, API requirements, and known limitations, see BROWSER_COMPATIBILITY.md.
MIT