A standalone Tauri v1 example that records a live WebGL canvas with the browser MediaRecorder pipeline and saves the resulting video through Tauri’s native file dialog.
This is the first export-focused project in the Tauri v1 essentials series. It demonstrates the complete path from a GPU-rendered browser canvas to a persistent desktop video file without adding a native encoder dependency.
HTMLCanvasElement.captureStream() for frame captureMediaRecorder codec feature detection__TAURI__.dialog.save__TAURI__.fs.writeBinaryFilenpm install
npm run dev
The project includes an explicit [workspace] boundary in src-tauri/Cargo.toml, so an unrelated Cargo workspace higher in the filesystem cannot absorb it.
The Save PNG button exports the current full-resolution WebGL frame independently from video recording.
WebGL fragment shader
│
▼
HTMLCanvasElement
│ captureStream(FPS)
▼
Canvas video track ──────────────┐
├──► MediaStream ─► MediaRecorder
Optional getUserMedia audio ─────┘ │
▼
Blob chunks
│
▼
Tauri save dialog + fs API
The CSS preview fills the available stage, but the canvas backing store remains at the selected recording resolution. For example, selecting 3840 × 2160 causes WebGL to render and record a true 4K canvas even if the visible application window is smaller.
Higher resolutions increase GPU and encoder cost. Start with 1920 × 1080 at 60 fps and 24 Mbps, then move to 4K after confirming that the local WebView can sustain the desired frame rate.
The example asks MediaRecorder.isTypeSupported() about several candidates and selects the first available option.
For a recording without microphone audio it tries:
video/webm;codecs=vp9
video/webm;codecs=vp8
video/mp4;codecs=h264
video/mp4
video/webm
When microphone audio is enabled, audio-bearing codec combinations are preferred. The active result appears in the Codec field before recording starts.
The exact output container is therefore platform- and WebView-dependent. The save dialog uses the extension that matches the recorder’s selected MIME type.
Microphone input is optional and disabled by default.
When enabled:
MediaStream.On macOS, Info.plist contains NSMicrophoneUsageDescription, and entitlements.plist enables audio input for bundled builds.
tauri.conf.json enables only the APIs needed by this example:
"allowlist": {
"all": false,
"dialog": { "save": true },
"fs": { "writeFile": true }
}
The matching Rust features are declared in src-tauri/Cargo.toml:
tauri = { version = "1", features = ["wry", "dialog-save", "fs-write-file"] }
The frontend receives the pre-bundled Tauri API through:
"withGlobalTauri": true
The completed Blob is converted to a Uint8Array only when the user chooses to save. This keeps the active recording path focused on MediaRecorder chunks rather than repeatedly sending large buffers through IPC.
This essentials example holds completed MediaRecorder chunks in memory until the recording is discarded or replaced. That keeps the architecture understandable, but very long or very high-bitrate captures can consume significant memory.
For a production recorder, common next steps are:
The internal generator provides four recording-ready modes:
The shader includes speed, scale, octave detail, warp, pulse, hue, saturation, brightness, and contrast controls. It exists so the recording pipeline can be tested immediately without loading external media.
14-tauri-v1-canvas-recorder/
├── package.json
├── README.md
├── src/
│ ├── index.html controls and WebGL stage
│ ├── styles.css compact recorder interface
│ └── app.js shader, capture, MediaRecorder, and saving
└── src-tauri/
├── Cargo.toml
├── Info.plist
├── entitlements.plist
├── tauri.conf.json
├── build.rs
├── icons/
└── src/main.rs
The WebView must expose both canvas.captureStream() and MediaRecorder. The UI performs this check at startup and displays an unsupported-state overlay when either API is absent.
The WebView did not accept any of the tested MIME strings. Upgrade the operating system/WebView and rerun the example. Codec availability is provided by the platform browser engine rather than Tauri itself.
Some systems hide device labels until microphone permission has been granted. Enable microphone audio and start one recording attempt; after permission succeeds, the list is refreshed automatically.
Reduce one or more of:
The FPS readout reports the live rendering loop, not a guaranteed encoded-frame count.
Confirm that both the Tauri allowlist entries and the Rust features remain present. The native save path depends on the dialog-save and fs-write-file APIs.
Rendering is only half of a live-visual application. Artists and developers also need to capture output, understand browser codec negotiation, mix optional audio, communicate recording state clearly, and move binary media from the WebView into the desktop filesystem. This project isolates that complete workflow in one small Tauri v1 application.