TL;DR: fal.ai handles AI video generation; Mux turns that output into something that streams everywhere with <MuxPlayer />.
With this combo, you can focus on prompts and products — not on video pipelines. Try the demo and skim the code if you want to see it before you read. https://github.com/muxinc/Mux-Fal
Mux works great for AI video companies. Many teams building generative AI models and products probably don't have time to build their own video infrastructure. That's where Mux comes in. Combined with fal.ai, there are endless possibilities for generative media applications. Try out the application here: https://fal.mux.dev/
This combination brings out the best of both platforms. When teams build generative AI video applications, they can use Mux as their go-to video API for encoding, storage, delivery, data & analytics and a fully-featured video player.
fal.ai brings together the world's best generative image, video, and audio models in one place. You can develop and fine-tune models with their serverless GPUs and on-demand clusters. For this example, we'll use the model APIs provided by fal. You can choose from tons of models—Sora-2, Kling-video, and Wan 2.5—but we're going with Veo 3.1 (Google’s AI Video Model). You could also use Veo 3.1 through the Gemini API or Sora through OpenAI, but fal gives you the ability to scale an app similar to the one I built with fal's serverless GPUs and dedicated compute clusters.
Overall, my experience integrating fal.ai with Mux was really straightforward. fal's inference engine is impressively fast, and the SDK was incredibly easy to get started with.
Give it a try
You can go ahead and use the app if you have a fal.ai API Key and some credits handy. Once you boot it up, the app will already be in demo mode. When demo mode is active, the /api/generate-video route responds immediately with the configured Mux asset and adds a notice to the UI. If you want to bypass demo mode, visitors can provide their own fal.ai API key in the UI field ("fal.ai API key (optional)"), which is used only for that request. Just make sure the asset referenced by DEMO_MUX_ASSET_ID has a public playback policy—the API will create one automatically if needed.
What this example shows
- Client-side polling for asset readiness. After the server creates the Mux asset from the fal.ai video, the client polls every 3 seconds to check when processing completes. Once the status is 'ready', the playbackId loads into <MuxPlayer /> and playback begins. Demo mode bypasses this entirely, returning a pre-generated asset immediately.
Prerequisites
- Node.js 18+
- pnpm (or whatever JS package manager you'd like)
- Mux account with API access (token ID/secret and a webhook signing secret)
- fal.ai API key
- Optional: ngrok for exposing the local development server to receive webhooks
This is not a step-by-step tutorial blog post. We’ll highlight some code and you can also check out the repo here.
The client: submit → poll → play
On submit, we clear the prior state, POST to /api/generate-video:
const handleSubmit = async () => {
if (!prompt.trim()) return;
stopPolling();
setIsLoading(true);
setError(null);
setGeneratedVideo(null);
setGeneratedVideoUrl(null);
setResultNotice(null);
setIsDemoResult(false);
setAssetStatus(null);
try {
const payload: Record<string, string> = { prompt: prompt.trim() };
const trimmedFalKey = falKey.trim();
if (trimmedFalKey) payload.falKey = trimmedFalKey;
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data: VideoGenerationResponse = await response.json();
if (!data.success) {
setError(data.error || 'Failed to generate video');
setIsLoading(false);
return;
}This mirrors the app’s actual client flow — state reset, POST, handle success/error, then render.
And the player is intentionally boring — which is perfect:
<div className="relative w-full overflow-hidden rounded-lg" style={{ aspectRatio: '16 / 9' }}>
<MuxPlayer
playbackId={generatedVideo}
poster={generatedPosterUrl}
className="h-full w-full"
autoPlay
muted
/>
</div>Drop in the playbackId you just received and you’re done.
The server: subscribe → ingest → return
When you're not in demo mode, the server uses a fal API key to generate the clip via falClient.subscribe() (which waits for fal.ai to complete generation), then immediately hands the video URL to Mux to create an Asset. The server returns right away with the asset ID and initial status—it does not wait for Mux processing to finish. The client then polls /api/asset-status to check when the Mux asset becomes ready.:
const result = await falClient.subscribe("fal-ai/veo3", {
input: {
prompt: trimmedPrompt,
aspect_ratio: "16:9",
duration: "8s",
generate_audio: false,
}
});
const sourceUrl = result.data?.video?.url;
if (typeof sourceUrl !== "string" || !sourceUrl) {
throw new Error("fal.ai response did not include a video URL to ingest into Mux.");
}
const asset = await muxClient.video.assets.create({
inputs: [{ url: sourceUrl }],
playback_policy: ['public'],
video_quality: 'basic',
});
return NextResponse.json({
success: true,
data: result,
muxAssetId: asset.id,
status: asset.status,
sourceUrl,
demoMode: false,
notice: userFalKey
? "Demo mode bypassed."
: undefined,
});Why fal.ai + Mux?
fal.ai gives you fast, ergonomic model access; Mux handles streaming, analytics, and a cross-platform player, so you don’t have to build delivery yourself. It’s a clean separation of concerns that lets you ship AI video features without inventing a video stack.
Run it your way
- Play with demo mode: it returns immediately with a configured Mux asset and annotates the UI so people know it was a demo result.
- Go live: add your fal key in the UI or set it in your env. The server will authenticate, generate, ingest, and return a ready-to-stream playbackId.
- What you’ll need: Node 18+, pnpm (or your favorite), Mux API creds (plus a webhook signing secret), and a fal key. ngrok is optional for local webhooks.
Make it your own
- Check out the repo and fork it to make it your own!
- Swap models or tweak generation parameters like aspect_ratio, duration, or generate_audio in the fal input — check out the server route at /api/generate-video/route.ts (lines 114-123) to edit that.
- Customize <MuxPlayer /> styling or controls later; the minimal default keeps the tutorial focused.
- Consider the polling pattern: This implementation uses client-side polling (every 3 seconds) to check asset status. For production, you might want to explore alternatives like webhooks or Server-Sent Events for more efficient status updates. Or, if you prefer the original "one request, instant playback" approach, modify the server to wait for the Mux asset to become ready before responding (though this ties up the connection longer).



