Tauri Video Not Playing? The Ultimate Solution for “Unsupported URL” on macOS
When developing a local macOS app with Tauri, if you try to play a local video using <video>, you’ll likely encounter the following error:
[Error] Failed to load resource: unsupported URL
Or you might see a black screen, hear audio only, or receive no visible error message.
❗ Common Failure Paths
convertFileSrc→ ❌ WebKit does not support Range requestsfetch → blob→ ❌ Triggers CORS; resource blocked- Modifying CSP → ❌ Does not solve the underlying protocol issue
🧠 Root Cause
- WebKit limitation: On macOS, Safari’s engine doesn’t support playing
<video>via theasset://protocol, though static assets like images still work. - Range request failure: Videos inherently make Range requests (e.g.,
bytes=102400-), butasset://cannot return partial content. - CORS restriction:
fetch("asset://...")is treated as cross-origin, and you can’t setAccess-Control-Allow-Origin. - Unregistered protocol: If the
stream://protocol isn’t registered with a handler in Rust, it will throw errors or fail silently. - Rendering issues: Even if loading succeeds, videos may still show a black screen due to CSS or codec incompatibility.
🧪 Reproduction Example
fetch("asset://video.mp4")
.then(res => res.blob())
.then(blob => video.src = URL.createObjectURL(blob));
Result:
[Error] Failed to load resource: unsupported URL
WebKit doesn’t support asset:// because:
•asset:// is Tauri’s built-in read-only protocol, backed by AppHandle::fs_scope().
• Its behavior is fixed—you can’t control response handling.
• It doesn’t support Range requests.
• It lacks proper MIME negotiation.
• It cannot add correct CORS headers.
• WebKit treats unknown protocols as unsupported URLs, especially in <video>, which requires strict handling.
✅ The correct approach: Create a custom stream:// protocol.
✅ WebKit supports stream:// because:
• It’s your own custom protocol registered in Tauri.
• You can tell WebKit exactly how to handle it:
• Set MIME type (to tell the browser “this is a video”).
• Support Range requests (for segmented video loading).
• Enable CORS headers.
• Add custom headers, caching, etc.
👉 In essence, stream:// is under your control, so you can make it behave like a protocol the browser loves.
✅ Step 1: Register the Protocol Handler in Rust
```Rust
tauri::Builder::default()
.register_uri_scheme_protocol("stream", |request| {
// 读取视频文件
// 处理 Range 请求
// 返回正确的 Content-Type(例如 video/mp4)和响应体
})
✅ Step 2: Use the stream:// Protocol in Frontend
<video controls src="stream://video.mp4" />
🧰 Why Images/Fonts Work but Videos Fail • Images, fonts, and small scripts are small, loaded once (no Range requests). • Videos and audio files are large, require segmented loading (Range), caching, and decoding.
When loading, elements like automatically send Range headers such as: Range: bytes=102400- But asset:// can only return the entire file at once, not partial data, thus breaking seek and resume functionality.
🧠 How a Tauri App Works (Simplified)
🧩 Frontend: • Your React/Vue pages are bundled as static assets. • They are loaded in the WebView via asset://.
🔧 Backend: • Rust provides native capabilities (filesystem, FFmpeg, database, etc.). • You can register custom protocols (e.g., stream://) to emulate an HTTP-like service.
🎞️ Rendering: • macOS → WebKit (Safari engine) • Windows → WebView2 (Chromium
⸻
🙋♂️ 常见问题答疑(FAQ)
❓ Why can’t I just use file:/// or asset://?
Because these protocols: • Don’t support Range requests. • Can’t handle MIME negotiation. • Trigger CORS when using fetch, leading to blocked resources.
⸻
❓ Video has audio but black screen — why?
Possible causes: • CSS: The element is hidden or transparent. • Codec: The format (e.g., H.264 10-bit) isn’t supported. • WebKit bug: Safari sometimes only plays the audio track.
⸻ ✅ Summary
💡 If you need to play large local files (videos/audio) in Tauri,you must implement a custom protocol handler that supports Range requests.This is the only way to bypass WebKit’s limitations and avoid CORS and MIME issues.