Clip composition tool with client-side recording, multi-clip grid, and media playback for Hiyve video conferencing.
npm install @hiyve/react-clips
Peer dependencies:
npm install @hiyve/react @hiyve/utilities @mui/material @mui/icons-material @emotion/react @emotion/styled react
Optional peer dependencies:
@hiyve/react-collaboration — host ClipComposition inside FileSessionHost for full file-management workflows@hiyve/react-media-player — enhanced clip playback with waveforms, regions, and volume controls (falls back to native <audio> / <video> elements)import { HiyveProvider } from '@hiyve/react';
import { ClipComposition } from '@hiyve/react-clips';
function App() {
return (
<HiyveProvider region="us-east-2">
<ClipComposition
enableRecording
enableDragReorder
maxClips={10}
onClipAdded={(clip) => console.log('Added:', clip.name)}
onError={(err) => console.error(err)}
/>
</HiyveProvider>
);
}
Tokens are generated automatically when your server uses
@hiyve/adminmiddleware. See@hiyve/reactforHiyveProviderconfiguration options.
| Component | Description |
|---|---|
ClipComposition |
Main composition component with recording, multi-clip grid, and auto-save |
CreateCompositionDialog |
Dialog for naming a new composition before creation |
ClipSettingsDialog |
Recording settings: microphone, camera and the capture profile (instrument / voice) for new clips |
ClipRecorder |
Recording panel for capturing audio and video clips |
ClipPlayer |
Single clip playback (wraps @hiyve/react-media-player or falls back to native elements) |
ClipGrid |
Responsive grid layout with drag-and-drop reorder |
ClipToolbar |
Composition controls (lock, play all, collaboration mode, recorder toggle) |
| Hook | Description |
|---|---|
useClipRecorder |
Records audio/video clips client-side. Manages device selection, the audio capture profile, recording lifecycle, and clip upload. |
useClipPersistence |
Auto-save composition data with configurable interval, manual save, and unsaved change tracking. |
| Function | Description |
|---|---|
generateCompositionId() |
Generate a unique composition ID |
generateClipId() |
Generate a unique clip ID |
getSupportedMimeType(mediaType) |
Get the first browser-supported MIME type for recording |
formatRecordingDuration(seconds) |
Format seconds as mm:ss string |
getFileExtensionFromMime(mimeType) |
Get file extension (.webm, .ogg, .mp4) from a MIME type |
createCompositionFile(client, options) |
Create and upload a new composition JSON file |
remuxFmp4ToMp4(blob, mimeType?) |
Remux fragmented MP4 to standard MP4. Use after recording to produce a seekable file for cross-browser playback. Returns the original blob unchanged if it's already flat or remuxing fails. |
clipCompositionDownloadHandler |
A file-manager download handler (<FileManager downloadHandlers={[clipCompositionDownloadHandler]} />). Downloading a clip composition saves its media instead of its JSON edit list: one clip becomes that clip's audio or video file, several become a ZIP of them. Declines (so the stored file is saved) when the composition has no fetchable clips |
buildStoredZip(entries, now?) / crc32(bytes) |
Minimal dependency-free ZIP writer (store method) used by the handler; usable for any "several files as one download" case |
| Prop | Type | Default | Description |
|---|---|---|---|
initialComposition |
ClipCompositionFile |
— | Initial composition data to load |
title |
string |
— | Composition title |
fileId |
string |
— | Existing file ID for updates |
fileLocation |
string |
'/Clips' |
Storage location for composition files |
enableAutoSave |
boolean |
true |
Auto-save composition changes |
enableRecording |
boolean |
true |
Show recording controls |
enableRegions |
boolean |
false |
Enable named regions on clip players |
enableAudioPassthrough |
boolean |
false |
Enable audio passthrough mode. Adds a per-clip passthrough toggle and a toolbar toggle that switches passthrough on every clip together |
enableDragReorder |
boolean |
true |
Enable drag-and-drop clip reordering |
maxClips |
number |
20 |
Maximum number of clips in the composition |
maxRecordingDuration |
number |
300 |
Maximum recording duration in seconds |
autoSaveInterval |
number |
5000 |
Auto-save interval in milliseconds |
showHeader |
boolean |
true |
Show header with title and save status |
showToolbar |
boolean |
true |
Show toolbar with controls |
readOnly |
boolean |
false |
Disable editing |
labels |
Partial<ClipCompositionLabels> |
— | Custom text labels |
icons |
Partial<ClipCompositionIcons> |
— | Custom icons (ReactNode) |
colors |
Partial<ClipCompositionColors> |
— | Custom color values |
styles |
Partial<ClipCompositionStyles> |
— | Custom style values |
onAutoSave |
(fileId: string) => void |
— | Called after auto-save completes |
onSaveError |
(error: Error) => void |
— | Called when a save error occurs |
onChange |
(composition: ClipCompositionFile) => void |
— | Called when the composition changes |
onClipAdded |
(clip: ClipItem) => void |
— | Called when a clip is added |
onClipRemoved |
(clipId: string) => void |
— | Called when a clip is removed |
onError |
(error: Error) => void |
— | General error callback |
onImportMedia |
() => void |
— | Shows an "Import Media" toolbar button next to the recorder toggle. Open your own file picker here and add the chosen files as clips via the ref's addClip — lets users bring in existing audio/video instead of recording. Hidden while read-only/locked or at maxClips |
maxHeight |
string | number |
— | Maximum container height |
sx |
SxProps<Theme> |
— | MUI sx styling prop |
ClipComposition exposes a ref with the following methods:
import { useRef } from 'react';
import { ClipComposition, type ClipCompositionRef } from '@hiyve/react-clips';
function MyEditor() {
const ref = useRef<ClipCompositionRef>(null);
return (
<>
<ClipComposition ref={ref} />
<button onClick={() => ref.current?.save()}>Save Now</button>
</>
);
}
| Method / Property | Type | Description |
|---|---|---|
save() |
() => Promise<string | null> |
Trigger a manual save, returns file ID |
isSaving |
boolean |
Whether currently saving |
hasUnsavedChanges |
boolean |
Whether there are unsaved changes |
lastSaved |
Date | null |
Timestamp of the last save |
fileId |
string | null |
Current file ID |
addClip(clip, options?) |
(clip: ClipItem, options?: AddClipOptions) => void |
Add a clip programmatically. No-op if a clip with the same id already exists. Pass { silent: true } when replaying a clip another participant recorded — suppresses onClipAdded (the "recorded here" notification) while onChange still fires |
removeClip(clipId, options?) |
(clipId: string, options?: RemoveClipOptions) => void |
Remove a clip by ID. No-op for an unknown id. Pass { silent: true } when replaying another participant's removal — suppresses onClipRemoved while onChange still fires |
getComposition() |
() => ClipCompositionFile |
Get the current composition data |
Record audio or video clips with device selection and max duration enforcement:
import { useClipRecorder } from '@hiyve/react-clips';
function MyRecorder() {
const recorder = useClipRecorder({
maxDuration: 120, // 2 minutes
onError: (err) => console.error(err),
});
return (
<div>
<p>State: {recorder.state}</p>
{recorder.state === 'idle' && (
<button onClick={recorder.initialize}>Initialize</button>
)}
{recorder.state === 'ready' && (
<button onClick={recorder.start}>Record</button>
)}
{recorder.state === 'recording' && (
<>
<span>{recorder.duration}s</span>
<button onClick={recorder.pause}>Pause</button>
<button onClick={recorder.stop}>Stop</button>
</>
)}
{recorder.state === 'paused' && (
<>
<button onClick={recorder.resume}>Resume</button>
<button onClick={recorder.stop}>Stop</button>
</>
)}
{recorder.state === 'stopped' && recorder.previewUrl && (
<>
<audio src={recorder.previewUrl} controls />
<button onClick={recorder.discard}>Discard</button>
</>
)}
</div>
);
}
Recorder states:
| State | Meaning |
|---|---|
idle |
Recorder has not been initialized — call initialize() |
requesting |
Requesting camera/microphone permission from the user |
ready |
Permissions granted and ready to record — call start() |
recording |
Currently recording — call pause() or stop() |
paused |
Recording paused — call resume() or stop() |
stopped |
Recording finished — review previewUrl or discard() |
saving |
Uploading the recorded clip |
Browsers capture microphone audio for phone calls by default: echo
cancellation, noise suppression and automatic gain, downmixed to mono. That
flattens an instrument or an audio interface into a compressed, band-limited
recording. Clips therefore record with an instrument profile unless the
user chooses otherwise: processing off, stereo and 48 kHz requested where the
device allows, tracks tagged as music, and the encoder set to 256 kbps stereo
(128 kbps mono) instead of the browser's voice-class default.
The voice profile keeps the browser's speech processing — the right
choice for spoken notes, and for recording over speakers, since instrument
does not remove speaker sound from the recording.
The user picks in ClipSettingsDialog; the choice is remembered. Set the
starting profile per context with the audioProfile prop on ClipComposition
/ ClipRecorder, or the option on useClipRecorder; the hook exposes
audioProfile and setAudioProfile.
// A composition used for spoken feedback, recorded over speakers
<ClipComposition audioProfile="voice" ... />
Auto-save composition data with retry logic:
import { useClipPersistence } from '@hiyve/react-clips';
const persistence = useClipPersistence({
client,
clips,
title: 'My Composition',
lockMode: 'unlocked',
collaborationMode: 'public',
enabled: true,
userId: 'user-123',
autoSaveInterval: 5000,
onSaved: (fileId) => console.log('Saved:', fileId),
onError: (err) => console.error(err),
});
// persistence.save() — trigger manual save
// persistence.isSaving — currently saving?
// persistence.hasUnsavedChanges — pending changes?
// persistence.lastSaved — last save timestamp
// persistence.fileId — current file ID
// persistence.markUnsaved() — mark as changed
For full file-management workflows (browse compositions, create new ones, switch between them) use FileSessionHost from @hiyve/react-collaboration with ClipComposition registered as the editor for the clip-composition resource type. See @hiyve/react-collaboration for details.
All visual aspects are customizable through four prop objects. Only override the keys you want to change — defaults are applied for the rest.
~40 text strings covering header, save status, toolbar, recorder, player, grid, collaboration, and error messages. Includes two functions: lastSaved(date) and clipCount(count).
<ClipComposition
labels={{
title: 'Sound Clips',
startRecording: 'Record',
noClips: 'No clips yet — start recording!',
lastSaved: (date) => `Saved at ${date.toLocaleTimeString()}`,
}}
/>
25 icon slots, each accepting a ReactNode. Defaults use @mui/icons-material.
import { MicNone, FiberManualRecord } from '@mui/icons-material';
<ClipComposition
icons={{
recordAudio: <MicNone />,
startRecording: <FiberManualRecord color="error" />,
}}
/>
24 color values for container, header, toolbar, recorder, grid, clip cards, save status, and collaboration mode indicators.
<ClipComposition
colors={{
containerBackground: '#2d2d2d',
recordingIndicator: '#e53935',
clipCardBackground: '#383838',
}}
/>
12 numeric/string values for border radius, padding, grid layout, and component dimensions.
<ClipComposition
styles={{
borderRadius: 12,
gridGap: 16,
gridMinColumnWidth: 320,
playerHeight: 150,
}}
/>
| Default Object | Merge Function |
|---|---|
defaultClipCompositionLabels |
mergeClipCompositionLabels(overrides?) |
defaultClipCompositionIcons |
mergeClipCompositionIcons(overrides?) |
defaultClipCompositionColors |
mergeClipCompositionColors(overrides?) |
defaultClipCompositionStyles |
mergeClipCompositionStyles(overrides?) |
defaultCreateCompositionDialogLabels |
mergeCreateCompositionDialogLabels(overrides?) |
@hiyve/react-media-player is installed)<audio> / <video> fallback when media player is not availableHiyveProvider| Constant | Value | Description |
|---|---|---|
DEFAULT_FILE_LOCATION |
'/Clips' |
Default storage location |
CLIP_COMPOSITION_FILE_EXTENSION |
'.json' |
Composition file extension |
CLIP_COMPOSITION_FILE_MIME_TYPE |
'application/json' |
Composition MIME type |
CLIP_RESOURCE_TYPE |
'clip-composition' |
Resource type identifier for composition files |
CLIP_VIDEO_RESOURCE_TYPE |
'clip' |
Resource type identifier for individual video clip files |
CLIP_AUDIO_RESOURCE_TYPE |
'clip-audio' |
Resource type identifier for individual audio clip files |
DEFAULT_AUTO_SAVE_INTERVAL |
5000 |
Auto-save interval (ms) |
DEFAULT_MAX_RECORDING_DURATION |
300 |
Default max recording (seconds) |
MAX_RECORDING_DURATION_HARD |
1800 |
Hard limit on recording (seconds) |
RECORDING_TIMESLICE |
1000 |
MediaRecorder timeslice (ms) |
DEFAULT_MAX_CLIPS |
20 |
Default max clips per composition |
DEFAULT_GRID_COLUMNS |
2 |
Default grid columns |
PREFERRED_AUDIO_MIME_TYPES |
string[] |
Audio MIME type preference order |
PREFERRED_VIDEO_MIME_TYPES |
string[] |
Video MIME type preference order |
COLLABORATION_MODES |
['private', 'public'] |
Available collaboration modes |
LOCK_MODES |
['locked', 'unlocked'] |
Available lock modes |
| Type | Description |
|---|---|
ClipItem |
A single clip with media type, file reference, duration, regions, and metadata |
ClipCompositionFile |
Full composition data: title, clips array, lock/collaboration modes, author info |
ClipMediaType |
'audio' | 'video' |
CompositionLockMode |
'locked' | 'unlocked' |
CollaborationMode |
'private' | 'public' |
RecorderState |
'idle' | 'requesting' | 'ready' | 'recording' | 'paused' | 'stopped' | 'saving' |
RegionData |
Named region for clip playback (id, start, end, content) |
GridLayoutItem |
Custom grid position for a clip |
ClipFileClient |
File client interface for upload and URL resolution |
ClipCompositionRef |
Imperative handle exposed by ClipComposition |
CreateCompositionDialogLabels |
Customizable labels for the create composition dialog |
CreateCompositionDialogProps |
Props for CreateCompositionDialog |
ClipSettingsDialogProps |
Props for ClipSettingsDialog |
TimelineMarkerData |
Marker data (id, time, content, optional authorId, authorName, createdAt, editedAt, color) attached to a clip |
Seven style-generating functions for building custom layouts:
| Function | Description |
|---|---|
getContainerStyles(colors, styles) |
Main container styles |
getHeaderStyles(colors, styles) |
Header bar styles |
getToolbarStyles(colors, styles) |
Toolbar styles |
getRecorderStyles(colors, styles) |
Recorder panel styles |
getGridStyles(colors, styles) |
CSS Grid layout styles |
getClipCardStyles(colors, styles, isDragging?) |
Individual clip card styles |
getEmptyStateStyles(colors) |
Empty state placeholder styles |
@mui/material, @mui/icons-material, @emotion/react, @emotion/styled)@hiyve/react — provides HiyveProvider context (components work outside the provider with limited functionality)@hiyve/utilities — debug logging and retry utilities
@hiyve/react-clips — Clip composition tool with client-side recording, multi-clip grid, and media playback for Hiyve video conferencing.