fluent-streamer
Fluent Streamer
Fluent FFmpeg wrapper for TypeScript runtimes, v0.5.1
Offers a fluent, chainable API for media/audio/video processing with FFmpeg, supporting streams, crossfade, audio effects, timeouts, and progress tracking.
- TypeScript-first: typed, chainable, and modern
- Cross-runtime: Node.js, Bun, Deno, and Browser (via
@ffmpeg/ffmpeg) - Web Streams: native
ReadableStream/WritableStreamwith Node.js stream compatibility - Audio processing: built-in EQ, volume, compression controls
- Human-friendly: good defaults and self-describing API
- Advanced features: crossfade, format conversion, codec control, kill/timeout, progress
Powered by FFmpeg.
Works in Node.js, Bun, Deno, and the browser. Streaming does not require files on disk.
Installation
# From npm (when published)
npm install fluent-streamer
# From GitHub (latest)
npm install github:dirold2/fluent-streamer
# Using Yarn
yarn add github:dirold2/fluent-streamer
# Using PNPM
pnpm install github:dirold2/fluent-streamer
# Using Bun
bun install github:dirold2/fluent-streamerBrowser (optional): install FFmpeg WASM bindings alongside the package:
npm install @ffmpeg/ffmpeg @ffmpeg/coreThe browser runner is selected automatically when window is defined.
Migrating from 0.4.x
| 0.4.x | 0.5.x |
|---|---|
import FluentStream from "fluent-streamer" |
import { FluentStream } from "fluent-streamer" |
const { done } = fs.run() |
const { done } = await fs.run() |
output: Readable (Node.js) |
output: ReadableStream<Uint8Array> |
.output(fs.createWriteStream(...)) |
.output("file.wav") or pipe output from .run() |
Usage
import { FluentStream } from "fluent-streamer";
// Basic conversion example
const fs = new FluentStream()
.input("input.wav")
.audioCodec("aac")
.audioBitrate("192k")
.output("output.m4a");
const { done } = await fs.run();
await done;
console.log("Conversion finished.");Stream Processing
import { FluentStream } from "fluent-streamer";
import fs from "node:fs";
// Node.js streams are accepted as input (adapted to Web Streams internally)
const input = fs.createReadStream("track.mp3");
const f = new FluentStream()
.input(input)
.format("wav")
.output("new.wav");
await f.run();
console.log("Done streaming!");For piped output, use pipe:1 and consume the returned stream:
const { output, done } = await new FluentStream()
.input(input)
.format("wav")
.output("pipe:1")
.run();
// Pipe Web Stream to a destination (Node.js 18+)
import { Writable } from "node:stream";
const file = fs.createWriteStream("new.wav");
await output.pipeTo(Writable.toWeb(file));
await done;Crossfade Example
import { FluentStream } from "fluent-streamer";
await new FluentStream()
.input("a.mp3")
.input("b.mp3")
.crossfadeAudio(2.5) // 2.5 seconds crossfade
.output("x.mp3")
.run();Advanced Streaming Example
import { FluentStream } from "fluent-streamer";
import { Readable } from "node:stream";
// Stream from HTTP source to response
app.get("/audio/:fileId", async (req, res) => {
const audioUrl = `https://cdn.example.com/audio/${req.params.fileId}.mp3`;
try {
const streamer = new FluentStream()
.input(audioUrl)
.setHeaders({
Authorization: "Bearer token",
"X-Client-Id": "my-app",
})
.audioCodec("aac")
.audioBitrate("128k")
.format("mp3")
.output(FluentStream.stdout);
const { output, done } = await streamer.run();
res.setHeader("Content-Type", "audio/mpeg");
res.setHeader("Content-Disposition", 'inline; filename="converted.mp3"');
Readable.fromWeb(output).pipe(res);
await done;
} catch (error) {
console.error("Audio stream error:", error);
res.status(500).send("Streaming failed");
}
});Real-time Audio Effects
import { FluentStream } from "fluent-streamer";
// Create streamer for live audio processing
const streamer = new FluentStream({
enableProgressTracking: true,
useAudioProcessor: true,
});
const inputStream = getAudioSource(); // ReadableStream<Uint8Array> or Node.js Readable
const { output, done } = await streamer
.input(inputStream)
.audioCodec("pcm_s16le")
.audioFrequency(44100)
.audioChannels(2)
.output("pipe:1")
.run();
// Adjust audio effects in real-time (property accessors or change* methods)
streamer.volume = 1.5; // Boost volume by 50%
streamer.bass = 5; // Increase bass
streamer.treble = -3; // Reduce treble
// Listen for progress events
streamer.on("progress", (progress) => {
console.log(`Processing: ${progress.progress}%`);
});
await done;Fade Effects and Transitions
import { FluentStream } from "fluent-streamer";
// Fade in/out example
const streamer = new FluentStream()
.input("background-music.mp3")
.setVolume(0) // Start silent
.output("fade-demo.mp3");
const { done } = await streamer.run();
// Fade in over 2 seconds
await new Promise((resolve) => setTimeout(resolve, 1000));
streamer.fadeIn(1, 2000); // Target volume 1, fade time 2000ms
await new Promise((resolve) => setTimeout(resolve, 8000));
streamer.fadeOut(3000); // Fade out over 3 seconds
await done;Audio Effects Control
import { FluentStream } from "fluent-streamer";
const streamer = new FluentStream({
enableProgressTracking: true,
useAudioProcessor: true,
})
.input("music.mp3")
.setVolume(1.5) // Increase volume by 50%
.setBass(8) // Boost bass
.setTreble(-3) // Cut treble slightly
.setCompressor(true) // Enable dynamic compression
.output("enhanced.wav");
const { done } = await streamer.run();
await done;API Highlights
FluentStream provides a fluent interface:
import { FluentStream } from "fluent-streamer";
await new FluentStream()
.input("song.mp3")
.seekInput(30) // seek to 30s
.audioCodec("opus")
.audioBitrate("128k")
.output("clip.opus")
.run();Main methods:
.input(src)— add file/URL/stream input (string,ReadableStream<Uint8Array>, or Node.js Readable).output(dst)— set the output (file path, pipe object, or fd).audioCodec(codec)/.videoCodec(codec)— set codecs.audioBitrate(bps)/.videoBitrate(bps).format(fmt)— set output format (e.g. 'mp3', 'wav').seekInput(time)— seek input position.overwrite()— overwrite output file(s).map(spec)— select specific streams.crossfadeAudio(seconds, options?)— crossfade (audio).run()— start processing (async; returnsoutput,done,stop)
Complete API Reference
Constructor
new FluentStream(options?: ProcessorOptions)Creates a new FluentStream instance with optional processor options.
Options:
timeout?: number- FFmpeg process timeout in secondsenableProgressTracking?: boolean- Enable progress event emissionfailFast?: boolean- Stop processing on first errorwallTimeLimit?: number- Maximum wall clock time limituseAudioProcessor?: boolean- Enable built-in audio processingaudioProcessorOptions?: AudioProcessingOptions- Audio effect defaults (volume, bass, treble, compressor, sampleRate, channels)logger?: Logger- Custom logger instance (debug, info, warn, error)verbose?: boolean- Enable debug log output
Input/Output Methods
.input(source: string | ReadableStream<Uint8Array> | NodeJS.Readable, options?: InputOptions)
Add file, URL, blob URL, or stream input.
// File input
.input('/path/to/audio.mp3')
// HTTP URL with custom headers
.input('https://cdn.com/track.mp3')
// Web Stream input
.input(readableStream)
// Node.js stream input (adapted internally)
.input(fs.createReadStream('input.wav'), { pipeIndex: 0 })Options:
label?: string- Input label for identificationpipeIndex?: number- Custom pipe index for streamsallowDuplicate?: boolean- Allow duplicate inputs
.output(destination: string | number | PipeObject)
Set output destination.
// File output (auto-overwrites if .overwrite() called)
.output('/path/to/output.mp4')
// Stdout / pipe
.output(FluentStream.stdout)
.output({ pipe: 'pipe:1' })For stream consumers, use pipe:1 and read the output stream returned by .run().
Audio/Video Configuration
.audioCodec(codec: string)
Set audio codec: 'aac', 'mp3', 'opus', 'vorbis', 'pcm_s16le', etc.
.videoCodec(codec: string)
Set video codec: 'h264', 'h265', 'vp9', 'av1', 'mpeg4', etc.
.audioBitrate(bitrate: string)
Set audio bitrate: '128k', '192k', '320k', 'variable', etc.
.videoBitrate(bitrate: string)
Set video bitrate: '1M', '2M', '5M', '10M' for high-quality, etc.
.format(format: string)
Set output container format: 'mp3', 'mp4', 'wav', 'flac', 'webm', etc.
.audioFrequency(frequency: number)
Set sample rate: 44100, 48000, 96000, etc.
.audioChannels(channels: number)
Set channel count: 1 (mono), 2 (stereo), 6 (5.1), 8 (7.1), etc.
Processing Controls
.seekInput(position: number | string)
Seek to position in input: 30, '00:00:30', '1:30', etc.
.duration(time: number | string)
Limit output duration.
.map(spec: string)
Select specific streams (advanced FFmpeg feature):
.map('0:v') // Select first video stream
.map('1:a') // Select second audio stream
.map('0') // Select all streams from first input.noVideo(), .noAudio()
Disable video/audio processing entirely.
.overwrite()
Allow overwriting existing output files.
.copyCodecs()
Copy streams without re-encoding (faster, preserves quality).
Audio Effects (Real-time capable)
.setVolume(value: number)
Set volume multiplier (0-2): 0.5 (half), 1.0 (normal), 1.5 (50% boost).
.setBass(value: number)
Adjust bass level (-20 to 20): 0 (neutral), 5 (boost), -3 (cut).
.setTreble(value: number)
Adjust treble level (-20 to 20): 0 (neutral), 5 (boost), -3 (cut).
.setCompressor(enabled: boolean)
Enable/disable audio compression for consistency.
.setEqualizer(bass, treble, compressor)
Set all EQ parameters at once.
.fadeIn(targetVolume?: number, durationMs?: number)
Fade in from current volume to target volume.
.fadeOut(durationMs?: number)
Fade out to silence.
Real-time changes (during playback):
.changeVolume(value: number)— boolean success.changeBass(value: number)— boolean success.changeTreble(value: number)— boolean success.changeCompressor(enabled: boolean)— boolean success.changeEqualizer(bass, treble, compressor)— boolean success
Advanced Features
.crossfadeAudio(duration: number, options?)
Create crossfade between inputs using FFmpeg's acrossfade filter.
.crossfadeAudio(2.5, {
curve1: 'tri', // crossfade curve
curve2: 'tri',
overlap: true,
secondInput: 'path/to/second.mp3'
}).complexFilter(graph: string | string[])
Add complex FFmpeg filter graphs for advanced processing.
Options & Configuration
.setHeaders(headers: Record<string, string>)
Set custom HTTP headers for remote sources.
.userAgent(userAgent: string)
Set User-Agent header for HTTP requests.
.globalOptions(...args), .inputOptions(...args), .outputOptions(...args)
Add FFmpeg arguments at specific positions.
Runtime Control
.run(options?) → Promise<FFmpegRunResultExtended>
Start processing asynchronously. Returns:
{
output: ReadableStream<Uint8Array>, // Output stream when piped
done: Promise<void>, // Settles exactly once on completion/failure
stop: () => void, // User stop/kill helper
passthrough: ReadableStream<Uint8Array>,
close: () => Promise<void> | void,
setVolume?, setBass?, setTreble?, setCompressor?, setEqualizer?, startFade?
}done resolves for normal process completion and intentional user stop()/close() calls. It rejects for spawn/process errors, non-zero FFmpeg exits and timeouts. In Node.js, child process errors such as spawn ffmpeg ENOENT are surfaced as process errors instead of ambiguous code === null exits.
.clear()
Reset instance for reuse (required before .run() after previous use).
.isDirtyState(), .isReady()
Check instance state.
Instance Events
'progress'— Progress updates (ifenableProgressTracking: true)'error'— Processing errors'complete'— Processing finished'start'— Processing started
Static Methods
Static Pipes
FluentStream.stdout— { pipe: 'stdout' }FluentStream.stderr— { pipe: 'stderr' }FluentStream.pipe1— { pipe: 'pipe:1' }FluentStream.pipe2— { pipe: 'pipe:2' }
Advanced
- Timeout: Set the
timeoutoption to auto-kill long FFmpeg jobs; timeout termination rejectsdone. - Progress: Get real-time progress events by enabling
.options({ enableProgressTracking: true })and listening for"progress"events. - Headers: Default humanity headers are sent to FFmpeg HTTP(S) sources; override with
.setHeaders(obj). - Kill/close:
run()returnsstop()andclose()helpers for intentional user termination; these are tracked separately from FFmpeg errors.
Types & Extensibility
- Written in TypeScript: All primary objects (
FluentStream,Processor) are strongly typed - Modular layout:
Fluent/(API),Core/(execution),Runner/(platform FFmpeg),Audio/(effects) - Built-in audio processing: Volume, EQ, compression via
AudioProcessorandAudioEffectController - Extensible runners: Implement
FFmpegRunnerfor custom process spawning or blob resolution
Troubleshooting
Common Issues
FFmpeg not found:
Error: spawn ffmpeg ENOENT
- Ensure FFmpeg is installed and accessible in PATH
- On Linux/macOS:
which ffmpeg - On Windows: Check if ffmpeg.exe is in your PATH
- Alternative: Provide full path with
new FluentStream({ ffmpegPath: '/usr/bin/ffmpeg' })
Stream errors with HTTP sources:
[tcp @ 0x...] Connection refused
- Verify URL is accessible:
curl -I https://example.com/audio.mp3 - Check network connectivity and firewall settings
- Some servers block requests without proper headers
Codec/format not supported:
Unknown encoder 'xxx' or unsupported codec
- Verify FFmpeg installation supports the codec:
ffmpeg -codecs | grep xxx - Some codecs require additional FFmpeg builds (e.g., non-free codecs)
- Try simpler codecs like
aacfor audio,h264for video
Out of memory during processing:
Cannot allocate memory
- Reduce input resolution/frame rate for videos
- Use
copyCodecs()for passthrough where quality loss is acceptable - Process in smaller chunks for large files
- Increase system memory limits if possible
File locking/permissions issues:
Permission denied (file locked)
- Ensure output directory is writable
- Close file handles before processing
- On Windows, ensure files aren't open in other applications
- Use unique temporary filenames
Debugging Tips
Enable verbose logging:
const streamer = new FluentStream({
logger: console, // Enable console logging
verbose: true, // Show debug messages
});Inspect FFmpeg command:
const streamer = new FluentStream()
.input('in.mp3')
.audioCodec('aac')
.output('out.m4a');
console.log('FFmpeg args:', streamer.getArgs());
console.log('Inputs:', streamer.getInputSummary());Check instance state:
console.log('Is dirty:', streamer.isDirtyState());
console.log('Is ready:', streamer.isReady());
console.log('Debug info:', streamer.debugInfo());Performance Optimization
For real-time streaming:
- Use high priority for FFmpeg processes
- Optimize audio-only processing with
.noVideo() - Use efficient codecs like
aacinstead ofmp3for streaming - Implement buffering to prevent stutter
- Monitor system resources during peak usage
For file processing:
- Use multiple CPU cores with FFmpeg threading:
.outputOptions('-threads', '0') - Process related tasks in parallel with separate FluentStream instances
- Cache processed results when possible
- Use faster presets:
.outputOptions('-preset', 'fast')
Memory management:
- Use streams for large files instead of loading entire content
- Clean up instances with
.clear()after use - Limit concurrent FFmpeg processes
- Monitor memory usage in production
Compatibility
Runtimes: Node.js 18+, Bun, Deno, Browser (with @ffmpeg/ffmpeg)
FFmpeg versions: 4.0+ required, 5.0+ recommended (native runtimes)
OS support: Linux, macOS, Windows, Docker containers
Known limitations:
- Real-time effects work best with PCM audio streams
- Some advanced filters may not support all input combinations
- HTTP streaming may have buffering delays depending on network
- Large crossfades can be memory-intensive
Contributing
Development Setup
git clone https://github.com/Dirold2/fluent-streamer.git
cd fluent-streamer
npm install
npm run build
npm testCode Style
- TypeScript: Strict typing required, oxlint compliant
- Naming: CamelCase for classes, camelCase for methods/properties
- Documentation: JSDoc comments for all public APIs
- Tests: Vitest-based, aim for >90% coverage
Project Structure
src/
Fluent/ — FluentStream builder API
Core/ — Processor, ThrottleStream, utilities
Runner/ — FFmpegRunner + platform runners (node, bun, deno, browser)
Audio/ — AudioProcessor, AudioEffectController
Types/ — Shared TypeScript interfaces
Adding Features
- Create issue on GitHub for new features
- Implement in appropriate module (Core, Types, etc.)
- Add tests for new functionality
- Update documentation (README, JSDoc)
- Ensure no breaking changes without major version bump
Audio Processing Extensions
The AudioProcessor class provides built-in audio effects that can be extended:
- Real-time effects: Volume, EQ, compression available during playback
- Custom AudioProcessor: Extend
AudioProcessorfor new effects - Integration: Effects are applied to PCM audio streams in the processing pipeline
Reporting Issues
When reporting bugs, please include:
- Node.js and FFmpeg versions
- OS and architecture
- Minimal reproducible example
- Expected vs actual behavior
- Debug output if available
Features Roadmap
- Enhanced video processing filters
- Improved browser/WASM audio processing
- GPU acceleration for video encoding
- Advanced audio analysis features
- Streaming server integrations
- Plugin system for custom filters and processors
License
MIT dirold2