video-messaging
Async video messaging — record screen + camera, upload to MUX, auto-transcribe, AI-summarize, and view in threaded conversations. Use this skill when the user says "add video messages", "setup video messaging", "async video", "video threads", "loom clone", or "video-messaging".
What this skill does
# Video Messaging
Asynchronous video messaging with browser-based recording (screen + camera + audio), MUX-hosted playback, automatic transcription, AI-generated summaries, and threaded conversations. Think Loom-style video messages with rich post-processing.
## Prerequisites
- Next.js app with `src/` directory and App Router
- `stream-mux` skill applied (MUX upload + asset management)
- `video-player` skill applied (MUX Player React components)
- `db` skill applied (Drizzle ORM + Postgres)
- `auth` skill applied (better-auth for user sessions)
- `queue` skill applied (Inngest for background transcription + AI summary jobs)
- `storage` skill applied (for supplementary file storage)
## Installation
No additional packages required. This skill uses:
- `MediaRecorder` browser API (built into all modern browsers)
- `navigator.mediaDevices.getDisplayMedia` for screen capture
- `navigator.mediaDevices.getUserMedia` for camera + audio
- `@mux/mux-node` (from `stream-mux` dependency)
- `@mux/mux-player-react` (from `video-player` dependency)
## What Gets Created
```
src/
├── lib/
│ ├── video/
│ │ ├── video-message.ts # Server: CRUD for video messages
│ │ ├── use-recorder.ts # Client hook: screen + camera recording
│ │ └── types-messaging.ts # VideoMessage, VideoThread types
│ └── db/
│ └── schema/
│ └── video-messages.ts # Drizzle schema: video_messages table
├── components/
│ └── video/
│ ├── video-recorder.tsx # Record UI with camera preview + controls
│ ├── video-message-card.tsx # Message card with summary + transcript
│ └── video-thread.tsx # Threaded conversation of video messages
└── app/
└── api/
└── video/
└── messages/
├── route.ts # POST create, GET list
└── [id]/
└── route.ts # GET message with details, GET replies
```
## Setup Steps
### Step 1: Create `src/lib/video/types-messaging.ts`
```typescript
export type VideoMessageStatus =
| "recording"
| "uploading"
| "processing"
| "transcribing"
| "ready"
| "errored";
export type VideoMessage = {
id: string;
userId: string;
title: string;
muxAssetId: string | null;
muxPlaybackId: string | null;
transcriptId: string | null;
meetingNoteId: string | null;
parentId: string | null;
duration: number | null;
status: VideoMessageStatus;
createdAt: Date;
};
export type VideoMessageWithDetails = VideoMessage & {
transcript: string | null;
summary: string | null;
actionItems: string[] | null;
senderName: string | null;
senderEmail: string | null;
senderImage: string | null;
replyCount: number;
};
export type VideoThread = {
message: VideoMessageWithDetails;
replies: VideoMessageWithDetails[];
};
export type RecorderOptions = {
screen: boolean;
camera: boolean;
audio: boolean;
};
export type RecorderState = {
isRecording: boolean;
isPaused: boolean;
duration: number;
previewStream: MediaStream | null;
cameraStream: MediaStream | null;
recordedBlob: Blob | null;
error: string | null;
};
```
### Step 2: Create `src/lib/video/use-recorder.ts`
```typescript
"use client";
import { useState, useCallback, useRef, useEffect } from "react";
import type { RecorderOptions, RecorderState } from "@/lib/video/types-messaging";
type UseRecorderReturn = RecorderState & {
startRecording: (options: RecorderOptions) => Promise<void>;
stopRecording: () => void;
pauseRecording: () => void;
resumeRecording: () => void;
resetRecording: () => void;
};
function useRecorder(): UseRecorderReturn {
const [isRecording, setIsRecording] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [duration, setDuration] = useState(0);
const [previewStream, setPreviewStream] = useState<MediaStream | null>(null);
const [cameraStream, setCameraStream] = useState<MediaStream | null>(null);
const [recordedBlob, setRecordedBlob] = useState<Blob | null>(null);
const [error, setError] = useState<string | null>(null);
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const chunksRef = useRef<Blob[]>([]);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const startTimeRef = useRef<number>(0);
const pausedDurationRef = useRef<number>(0);
// Clean up timer on unmount
useEffect(() => {
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
const stopAllTracks = useCallback((stream: MediaStream | null) => {
if (stream) {
for (const track of stream.getTracks()) {
track.stop();
}
}
}, []);
const startTimer = useCallback(() => {
startTimeRef.current = Date.now() - pausedDurationRef.current * 1000;
timerRef.current = setInterval(() => {
const elapsed = (Date.now() - startTimeRef.current) / 1000;
setDuration(Math.floor(elapsed));
}, 100);
}, []);
const stopTimer = useCallback(() => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}, []);
const startRecording = useCallback(
async (options: RecorderOptions) => {
setError(null);
setRecordedBlob(null);
chunksRef.current = [];
pausedDurationRef.current = 0;
try {
const tracks: MediaStreamTrack[] = [];
let screenStream: MediaStream | null = null;
let camStream: MediaStream | null = null;
// Get screen capture if requested
if (options.screen) {
screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 },
},
audio: options.audio,
});
for (const track of screenStream.getVideoTracks()) {
tracks.push(track);
}
// Use screen audio if available
if (options.audio) {
for (const track of screenStream.getAudioTracks()) {
tracks.push(track);
}
}
}
// Get camera + microphone if requested
if (options.camera || (options.audio && !options.screen)) {
const constraints: MediaStreamConstraints = {};
if (options.camera) {
constraints.video = {
width: { ideal: 640 },
height: { ideal: 480 },
facingMode: "user",
};
}
if (options.audio && !screenStream?.getAudioTracks().length) {
constraints.audio = {
echoCancellation: true,
noiseSuppression: true,
};
}
camStream = await navigator.mediaDevices.getUserMedia(constraints);
setCameraStream(camStream);
// If we have screen capture, only add audio from camera (not video)
if (options.screen) {
if (options.audio) {
for (const track of camStream.getAudioTracks()) {
tracks.push(track);
}
}
} else {
for (const track of camStream.getTracks()) {
tracks.push(track);
}
}
}
if (tracks.length === 0) {
throw new Error("No media tracks available. Enable screen, camera, or audio.");
}
const combinedStream = new MediaStream(tracks);
setPreviewStream(combinedStream);
// Determine best MIME type
const mimeType = MediaRecorder.isTypeSupported("video/webm;codecs=vp9,opus")
? "video/webm;codecs=vp9,opus"
: MediaRecorder.isTypeSupported("video/webm;codecs=vp8,opus")
? "video/webm;codecs=vp8,opus"
: "video/webm";
const recorder = new MediaRecorder(combinedStream, {
mimeType,
videoBitsPerSecond: 2_500_000,
});
recorder.ondataavailable Related in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.