Overview
Video Actions provide server-side functions for handling video uploads to S3, processing callbacks, and generating secure playback URLs. These are Convex actions that integrate with external services.
All video action functions are defined in convex/videoActions.ts.
Upload Functions
getUploadUrl
Generates a presigned S3 upload URL for direct client-to-S3 uploads.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
function VideoUploader({ videoId }) {
const getUploadUrl = useAction(api.videoActions.getUploadUrl);
const handleUpload = async (file: File) => {
const { uploadUrl, s3Key } = await getUploadUrl({ videoId });
// Upload directly to S3
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
console.log('Uploaded to S3:', s3Key);
};
return <input type="file" onChange={(e) => handleUpload(e.target.files[0])} />;
}
The video record to generate upload URL for
Presigned S3 URL for PUT upload
The S3 object key where the file will be stored
Permissions: Requires member role in the video’s team
Implementation: convex/videoActions.ts:15
markUploadComplete
Marks a video upload as complete and triggers Mux processing.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
async function finalizeUpload(videoId, s3Key) {
const markComplete = useAction(api.videoActions.markUploadComplete);
await markComplete({
videoId,
s3Key,
});
console.log('Video processing started');
}
The video to mark as complete
The S3 key where the video was uploaded
Permissions: Requires member role in the video’s team
Implementation: convex/videoActions.ts:56
markUploadFailed
Marks a video upload as failed with an error message.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
async function handleUploadError(videoId, error) {
const markFailed = useAction(api.videoActions.markUploadFailed);
await markFailed({
videoId,
error: error.message,
});
}
The video to mark as failed
Error message describing what went wrong
Permissions: Requires member role in the video’s team
Implementation: convex/videoActions.ts:90
Playback Functions
getPlaybackUrl
Generates a signed Mux playback URL for team members.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
function SecureVideoPlayer({ videoId }) {
const getPlaybackUrl = useAction(api.videoActions.getPlaybackUrl);
const loadVideo = async () => {
const { playbackUrl, duration } = await getPlaybackUrl({ videoId });
// Use playbackUrl with video player
};
return <button onClick={loadVideo}>Load Video</button>;
}
The video to get playback URL for
Video duration in seconds
Permissions: Requires viewer role in the video’s team
Implementation: convex/videoActions.ts:122
getPlaybackSession
Generates a Mux playback session token for authenticated playback.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
async function startPlayback(videoId) {
const getSession = useAction(api.videoActions.getPlaybackSession);
const { playbackId, token } = await getSession({ videoId });
// Use with Mux Player
return { playbackId, token };
}
The video to create session for
JWT token for signed playback
Video duration in seconds
Permissions: Requires viewer role in the video’s team
Implementation: convex/videoActions.ts:145
getPublicPlaybackSession
Generates a playback session for public videos (no authentication required).
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
function PublicPlayer({ publicId }) {
const getSession = useAction(api.videoActions.getPublicPlaybackSession);
const loadVideo = async () => {
const session = await getSession({ publicId });
if (!session) return; // Video not public
// Use session.playbackId and session.token
};
return <button onClick={loadVideo}>Play</button>;
}
The public ID of the video
Mux playback ID, or null if not found/not public
JWT token for signed playback
Permissions: Public - no authentication required
Implementation: convex/videoActions.ts:185
getSharedPlaybackSession
Generates a playback session for videos accessed via share link.
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
function SharedPlayer({ grantToken }) {
const getSession = useAction(api.videoActions.getSharedPlaybackSession);
const loadVideo = async () => {
const session = await getSession({ grantToken });
if (!session) return; // Invalid grant
return { playbackId: session.playbackId, token: session.token };
};
return <button onClick={loadVideo}>Play</button>;
}
Mux playback ID, or null if grant is invalid
JWT token for signed playback
Permissions: Public - requires valid share grant token
Implementation: convex/videoActions.ts:215
getOriginalPlaybackUrl
Gets the original uploaded video URL (not Mux-processed).
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
async function getOriginal(videoId) {
const getOriginalUrl = useAction(api.videoActions.getOriginalPlaybackUrl);
const { url } = await getOriginalUrl({ videoId });
return url; // S3 presigned download URL
}
The video to get original URL for
Presigned S3 URL for downloading the original file
Permissions: Requires member role in the video’s team
Implementation: convex/videoActions.ts:170
getDownloadUrl
Generates a download URL for videos (respects share link permissions).
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
function DownloadButton({ videoId }) {
const getDownload = useAction(api.videoActions.getDownloadUrl);
const handleDownload = async () => {
const { url } = await getDownload({ videoId });
window.location.href = url;
};
return <button onClick={handleDownload}>Download Video</button>;
}
The video to generate download URL for
Permissions: Requires viewer role in the video’s team, or valid share grant with allowDownload
Implementation: convex/videoActions.ts:245
Usage Patterns
Complete Upload Flow
import { useAction, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function CompleteUploadFlow({ projectId }) {
const createVideo = useMutation(api.videos.create);
const getUploadUrl = useAction(api.videoActions.getUploadUrl);
const markComplete = useAction(api.videoActions.markUploadComplete);
const markFailed = useAction(api.videoActions.markUploadFailed);
const uploadVideo = async (file: File) => {
try {
// 1. Create video record
const videoId = await createVideo({
projectId,
title: file.name,
fileSize: file.size,
contentType: file.type,
});
// 2. Get upload URL
const { uploadUrl, s3Key } = await getUploadUrl({ videoId });
// 3. Upload to S3
const response = await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
if (!response.ok) throw new Error('Upload failed');
// 4. Mark complete to trigger Mux processing
await markComplete({ videoId, s3Key });
return videoId;
} catch (error) {
if (videoId) {
await markFailed({ videoId, error: error.message });
}
throw error;
}
};
return <input type="file" onChange={(e) => uploadVideo(e.target.files[0])} />;
}
Secure Playback with Session Tokens
import { useAction } from 'convex/react';
import { api } from '../convex/_generated/api';
import MuxPlayer from '@mux/mux-player-react';
function SecurePlayer({ videoId }) {
const [session, setSession] = useState(null);
const getSession = useAction(api.videoActions.getPlaybackSession);
useEffect(() => {
getSession({ videoId }).then(setSession);
}, [videoId]);
if (!session) return <div>Loading...</div>;
return (
<MuxPlayer
playbackId={session.playbackId}
tokens={{ playback: session.token }}
metadata={{ video_title: 'Team Video' }}
/>
);
}