-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix(linux): fix video and audio playback on WebKitGTK (#4412) #4782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3-alpha
Are you sure you want to change the base?
Conversation
On Linux, WebKitGTK uses GStreamer for media playback. GStreamer doesn't have a URI handler for the wails:// protocol, causing video and audio elements to fail loading from bundled assets. This fix automatically intercepts media elements and converts wails:// URLs to blob URLs by fetching content through fetch() (which works via WebKit's URI scheme handler) and creating object URLs. Changes: - Add platform-specific JS injection via /wails/platform.js endpoint - Move /wails/* route handling from BundledAssetServer to main AssetServer so it works regardless of user's asset handler choice - Add DisableGStreamerFix and EnableGStreamerCaching options to LinuxOptions - Update runtime to fetch /wails/platform.js before emitting ready event - Add audio-video example demonstrating media playback with npm runtime - Add Linux media playback documentation - Add GStreamer codec dependencies to installation docs See: https://bugs.webkit.org/show_bug.cgi?id=146351 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
WalkthroughThis PR implements a Linux WebKitGTK media playback workaround by introducing JavaScript-based URL interception that converts Changes
Sequence DiagramsequenceDiagram
participant Browser as Browser/<br/>Document
participant Runtime as Wails Runtime<br/>(index.ts)
participant AssetServer as Asset Server<br/>(assetserver.go)
participant Injector as Platform JS<br/>(assetserver_linux.js)
participant MediaEl as Video/Audio<br/>Elements
participant GStreamer as GStreamer<br/>(via wails://)
Note over Runtime,Injector: Application Startup
Runtime->>AssetServer: fetch(/wails/platform.js)
AssetServer->>AssetServer: getPlatformJS()<br/>applies config
AssetServer-->>Runtime: platform.js content
Runtime->>Browser: inject <script> tag
Injector->>MediaEl: scan for video/audio elements
Injector->>Injector: set up MutationObserver<br/>for dynamic elements
Note over Browser,GStreamer: Media Playback
Browser->>MediaEl: add <video src="wails://...">
Injector->>Injector: observe DOM mutation
Injector->>Injector: intercept src attribute
Injector->>AssetServer: fetch(wails:// or relative URL)
AssetServer-->>Injector: media file (blob)
Injector->>Injector: create blob URL
Injector->>MediaEl: replace src with blob URL
MediaEl->>GStreamer: load blob:<br/>stream
GStreamer-->>MediaEl: playback ✓
MediaEl-->>Browser: media event (play,<br/>timeupdate, etc.)
Note over Injector: Caching (if enabled)
Injector->>Injector: EnableGStreamerCaching<br/>stores blob URLs
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (6)
v3/internal/assetserver/assetserver_darwin.go (1)
12-13: Same note: initializeplatformJSto[]byte{}(consistency/clarity).docs/src/content/docs/quick-start/installation.mdx (1)
141-173: Fedora GStreamer package name is correct; no action needed.
The docs usegstreamer1-plugins-ugly-free, which is in official Fedora repositories and requires no additional setup. The separategstreamer1-plugins-uglypackage from RPM Fusion is not shown, so there is no issue here.WebKitGTK dev package guidance could note Ubuntu 22.04 alternative.
Ubuntu 22.04 (Jammy) offers bothlibwebkit2gtk-4.0-devandlibwebkit2gtk-4.1-dev. The docs currently show onlylibwebkit2gtk-4.0-dev, which works, but users targeting WebKitGTK 4.1 may preferlibwebkit2gtk-4.1-dev. Consider adding a note: "On Ubuntu 22.04, you can alternatively uselibwebkit2gtk-4.1-devfor the newer WebKitGTK 4.1 API."v3/internal/assetserver/assetserver_linux.js (1)
41-50: Consider using URL constructor for more robust path resolution.The current logic works for most cases but doesn't handle relative paths with
..segments. While the browser typically resolves these before the script sees them, using the nativeURLconstructor would be more robust:function toAbsoluteUrl(src) { - if (src.startsWith('wails://') || src.startsWith('http://') || src.startsWith('https://')) { - return src; - } - if (src.startsWith('/')) { - return window.location.origin + src; - } - const base = window.location.href.substring(0, window.location.href.lastIndexOf('/') + 1); - return base + src; + try { + return new URL(src, window.location.href).href; + } catch (e) { + // Fallback for invalid URLs + return src; + } }This handles all relative path cases (including
..) and leverages the browser's native URL resolution.v3/internal/assetserver/assetserver.go (3)
21-32: Add synchronization or document initialization-only usage for thread safety.The package-level variables
disableGStreamerFixandenableGStreamerCachingare modified bySetGStreamerOptionsand read ingetPlatformJS(called from HTTP handlers). While the comment indicates this is called during initialization, there's no enforcement to prevent concurrent modification.Consider one of these approaches:
Option 1: Use sync.Once for initialization (recommended)
-var ( - disableGStreamerFix bool - enableGStreamerCaching bool -) +var ( + gstreamerOptions struct { + disable bool + enableCaching bool + once sync.Once + } +) -func SetGStreamerOptions(disable, enableCaching bool) { - disableGStreamerFix = disable - enableGStreamerCaching = enableCaching -} +func SetGStreamerOptions(disable, enableCaching bool) { + gstreamerOptions.once.Do(func() { + gstreamerOptions.disable = disable + gstreamerOptions.enableCaching = enableCaching + }) +}Option 2: Add clear documentation
+// SetGStreamerOptions configures GStreamer workaround options on Linux. +// IMPORTANT: This MUST be called during application initialization before +// any HTTP handlers are invoked to avoid data races. -// This is called during application initialization.Based on learnings, platform-specific initialization patterns are acceptable in Wails, but clarity improves maintainability.
159-170: LGTM! Endpoint handler is straightforward and correct.The function properly serves runtime and platform JavaScript files with appropriate content-type headers. Ignoring
Write()errors is standard practice for HTTP handlers since connection errors are not recoverable.Optional enhancement: Consider adding cache headers for production performance (these files are loaded on every page load):
rw.Header().Set(HeaderContentType, "application/javascript") rw.Header().Set("Cache-Control", "public, max-age=3600") // Cache for 1 hourHowever, for development workflows where changes should be immediately visible, the current no-cache behavior is preferable.
172-191: Consider a more maintainable approach for conditional JS generation.The current implementation uses
bytes.ReplaceAllto toggle the caching flag, which works but is brittle—if the JavaScript format changes, this replacement could silently fail without indication.Alternative approaches for better maintainability:
Option 1: Template-based generation
import "text/template" var platformJSTemplate = template.Must(template.New("platform").Parse(string(platformJS))) func getPlatformJS() []byte { if disableGStreamerFix { return nil } var buf bytes.Buffer platformJSTemplate.Execute(&buf, map[string]bool{ "EnableCaching": enableGStreamerCaching, }) return buf.Bytes() }Then in the JS file:
const ENABLE_CACHING = {{.EnableCaching}};Option 2: Runtime configuration via global variable
Have the backend inject configuration that JavaScript reads at runtime.For the current "Chill" review standard, the existing approach is acceptable, but consider these alternatives for future maintenance.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
v3/examples/audio-video/frontend/dist/assets/index-DxRb0nj9.jsis excluded by!**/dist/**v3/examples/audio-video/frontend/dist/index.htmlis excluded by!**/dist/**v3/examples/audio-video/frontend/dist/wails.mp3is excluded by!**/dist/**,!**/*.mp3v3/examples/audio-video/frontend/dist/wails.mp4is excluded by!**/dist/**,!**/*.mp4v3/examples/audio-video/frontend/package-lock.jsonis excluded by!**/package-lock.jsonv3/examples/audio-video/frontend/public/wails.mp3is excluded by!**/*.mp3v3/examples/audio-video/frontend/public/wails.mp4is excluded by!**/*.mp4
📒 Files selected for processing (20)
docs/src/content/docs/guides/linux-media.mdx(1 hunks)docs/src/content/docs/quick-start/installation.mdx(1 hunks)v3/UNRELEASED_CHANGELOG.md(1 hunks)v3/examples/audio-video/README.md(1 hunks)v3/examples/audio-video/frontend/index.html(1 hunks)v3/examples/audio-video/frontend/package.json(1 hunks)v3/examples/audio-video/frontend/src/main.js(1 hunks)v3/examples/audio-video/frontend/vite.config.js(1 hunks)v3/examples/audio-video/main.go(1 hunks)v3/internal/assetserver/assetserver.go(4 hunks)v3/internal/assetserver/assetserver_android.go(1 hunks)v3/internal/assetserver/assetserver_darwin.go(1 hunks)v3/internal/assetserver/assetserver_ios.go(1 hunks)v3/internal/assetserver/assetserver_linux.go(1 hunks)v3/internal/assetserver/assetserver_linux.js(1 hunks)v3/internal/assetserver/assetserver_windows.go(2 hunks)v3/internal/assetserver/bundled_assetserver.go(0 hunks)v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts(1 hunks)v3/pkg/application/application_linux.go(2 hunks)v3/pkg/application/application_options.go(1 hunks)
💤 Files with no reviewable changes (1)
- v3/internal/assetserver/bundled_assetserver.go
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:29-30
Timestamp: 2025-08-08T10:25:32.415Z
Learning: For Wails v3 Linux nfpm template, Debian/Ubuntu WebKitGTK runtime package names differ: Debian 12 (Bookworm) uses libwebkit2gtk-4.1-0 while Ubuntu 22.04 (Jammy) uses libwebkit2gtk-4.0-37. Prefer an OR dependency "libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37" in v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl to support both.
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4484
File: v2/internal/frontend/utils/urlValidator.go:25-31
Timestamp: 2025-08-08T13:15:20.339Z
Learning: In Wails v2 (PR wailsapp/wails#4484), for BrowserOpenURL URL validation (v2/internal/frontend/utils/urlValidator.go), maintainers (APshenkin) prefer not to restrict schemes to an http/https allowlist because supported schemes may vary. The allowlist suggestion is declined; continue with the existing denylist approach and generic validation.
📚 Learning: 2025-01-15T22:33:30.639Z
Learnt from: fbbdev
Repo: wailsapp/wails PR: 4001
File: v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/function_single_internal/greetservice.ts:0-0
Timestamp: 2025-01-15T22:33:30.639Z
Learning: In the Wails framework's TypeScript bindings, the import path "/wails/runtime.js" is intentionally absolute and should not be changed to a relative path.
Applied to files:
v3/examples/audio-video/frontend/package.jsonv3/internal/runtime/desktop/@wailsio/runtime/src/index.tsv3/examples/audio-video/frontend/src/main.js
📚 Learning: 2025-08-08T09:13:16.916Z
Learnt from: APshenkin
Repo: wailsapp/wails PR: 4480
File: v2/internal/frontend/desktop/darwin/message.h:17-19
Timestamp: 2025-08-08T09:13:16.916Z
Learning: In Wails v2 bindings origin verification, processBindingMessage intentionally has different signatures across platforms: Darwin includes an isMainFrame bool (WKWebKit provides it), Linux uses two params (message, source) as WebKitGTK doesn’t expose main-frame info there, and Windows handles origin checks in Go via WebView2 sender/args without a C bridge. This divergence is acceptable/expected per maintainer (APshenkin).
Applied to files:
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/examples/window/main.go:472-475
Timestamp: 2024-10-08T22:11:37.054Z
Learning: In `v3/examples/window/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.
Applied to files:
v3/examples/audio-video/main.go
📚 Learning: 2025-08-08T10:25:32.415Z
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:29-30
Timestamp: 2025-08-08T10:25:32.415Z
Learning: For Wails v3 Linux nfpm template, Debian/Ubuntu WebKitGTK runtime package names differ: Debian 12 (Bookworm) uses libwebkit2gtk-4.1-0 while Ubuntu 22.04 (Jammy) uses libwebkit2gtk-4.0-37. Prefer an OR dependency "libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37" in v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl to support both.
Applied to files:
v3/UNRELEASED_CHANGELOG.mddocs/src/content/docs/quick-start/installation.mdx
📚 Learning: 2025-08-08T10:25:32.415Z
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:29-30
Timestamp: 2025-08-08T10:25:32.415Z
Learning: Ubuntu 22.04 (Jammy) official repos include both WebKitGTK runtimes: libwebkit2gtk-4.0-37 and libwebkit2gtk-4.1-0 (Universe). For nfpm templates, depend on the exact SONAME matching the built ABI (use libwebkit2gtk-4.1-0 when targeting 4.1; use libwebkit2gtk-4.0-37 when targeting 4.0) rather than an OR across ABIs.
Applied to files:
docs/src/content/docs/quick-start/installation.mdx
🧬 Code graph analysis (3)
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts (2)
v3/internal/assetserver/assetserver_linux.js (1)
response(57-57)v3/internal/assetserver/bundledassets/runtime.debug.js (1)
response(123-127)
v3/pkg/application/application_linux.go (1)
v3/internal/assetserver/assetserver.go (1)
SetGStreamerOptions(29-32)
v3/internal/assetserver/assetserver.go (1)
v3/internal/assetserver/common.go (1)
HeaderContentType(15-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
- GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
- GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
- GitHub Check: semgrep/ci
- GitHub Check: Analyze (go)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: Cloudflare Pages
🔇 Additional comments (23)
v3/examples/audio-video/README.md (1)
1-38: Verify the environment variable nameFRONTEND_DEVSERVER_URLmatches the Go example code. The README referencesFRONTEND_DEVSERVER_URL=http://localhost:5173 go run ., but this should be confirmed against the actual implementation in the example to ensure the variable name is correct.v3/internal/assetserver/assetserver_android.go (1)
14-15: No changes needed –platformJSinitialization is consistent across all platforms.All platform implementations (Windows, iOS, Linux, Android, Darwin) use the same uninitialized
var platformJS []bytepattern, which defaults to nil. No inconsistency exists that would warrant platform-specific changes.v3/examples/audio-video/frontend/package.json (1)
1-16: No action needed; localfile:dependency is correctly configured.The
file:../../../internal/runtime/desktop/@wailsio/runtimereference is already locked inpackage-lock.jsonwith proper resolution and the"link": trueflag, confirming npm resolves it correctly. Vite^5.0.0is consistent with the majority of examples across the repository.v3/internal/assetserver/assetserver_windows.go (1)
1-13: No changes needed. The Windows implementation is consistent with all other platform files (Linux, macOS, iOS, Android)—all declarevar platformJS []byteas uninitialized (nil). The sharedgetPlatformJS()function explicitly checksif platformJS == nilto gate platform-specific behavior, which is intentional design. Changing only Windows to[]byte{}would introduce platform-specific inconsistency without justification. If the nil/empty distinction needs to be addressed, it must be changed uniformly across all platform files simultaneously, not selectively.v3/examples/audio-video/frontend/vite.config.js (1)
1-8: Vite 5 build config is correct and safe for dist embedding.The configuration properly uses
defineConfigwithbuild.outDir: 'dist'andbuild.emptyOutDir: true. TheemptyOutDirsetting is redundant (Vite defaults totruewhenoutDiris inside the project root), but explicit declaration is harmless and improves clarity. No behavioral gotchas for embedding dist artifacts in non-SPA contexts; clean build output on each run is actually beneficial.docs/src/content/docs/guides/linux-media.mdx (1)
12-153: Guide is accurate and consistent with quick-start installation. Fedora package names (gstreamer1-plugins-good,gstreamer1-plugins-bad-free,gstreamer1-plugins-ugly-free) match the quick-start guide and are correct for Fedora's official repositories. RPM Fusion is not required for the basic setup but can optionally be enabled for wider codec coverage if needed. Consider adding a note that users seeking better codec support may optionally enable RPM Fusion for additional "freeworld" builds.v3/UNRELEASED_CHANGELOG.md (1)
19-20: LGTM! Clear changelog entries.The changelog entries properly document the new Linux media playback workaround and configuration options, with appropriate issue references and author credits.
Also applies to: 27-27
v3/pkg/application/application_linux.go (2)
27-27: LGTM! Proper import addition.The import of the internal assetserver package is necessary for the GStreamer configuration.
289-293: LGTM! Correct GStreamer option wiring.The call to
assetserver.SetGStreamerOptionsproperly wires the LinuxOptions fields to the asset server during platform app initialization. The comment clearly explains the purpose.v3/examples/audio-video/main.go (1)
1-35: LGTM! Clean example implementation.The example application is well-structured and demonstrates the audio/video playback functionality. It uses sensible defaults (GStreamer fix enabled) and includes Mac-specific termination behavior for better developer experience.
v3/internal/assetserver/assetserver_linux.go (2)
5-8: LGTM! Standard embed pattern.The addition of the embed import and package-level import is correctly implemented.
15-16: LGTM! Platform JS embedding.The go:embed directive correctly embeds the Linux-specific JavaScript workaround into the platformJS variable for runtime delivery.
v3/internal/assetserver/assetserver_ios.go (1)
12-13: LGTM! Consistent platform pattern.The empty platformJS variable with explanatory comment maintains consistency with the platform-specific JS provisioning pattern while clarifying that iOS doesn't require special handling.
v3/examples/audio-video/frontend/src/main.js (1)
1-40: LGTM! Clean media status tracking implementation.The frontend code properly tracks media loading states with event listeners and includes a reasonable 5-second timeout for detecting failures. The duration display and status updates provide good user feedback.
v3/examples/audio-video/frontend/index.html (1)
1-61: LGTM! Well-structured example frontend.The HTML provides a clean, dark-themed UI for demonstrating audio/video playback with proper semantic structure, accessible controls, and visual status indicators.
v3/pkg/application/application_options.go (1)
243-252: LGTM! Well-documented Linux media playback options.The new LinuxOptions fields are clearly named with comprehensive documentation. The default behaviors (fix enabled, caching disabled) are sensible choices for the typical use case. The referenced WebKit bug URL is accessible and provides appropriate context.
v3/internal/assetserver/assetserver_linux.js (5)
25-39: LGTM! URL interception logic is comprehensive.The function correctly identifies URLs that need conversion while skipping already-processed blob/data URLs. The logic covers wails:// protocol, relative paths, and same-origin URLs.
69-86: LGTM! Source element processing is well-structured.The function correctly:
- Prevents duplicate processing with
WeakSet- Preserves original URLs in
dataset.wailsOriginalSrcfor debugging- Handles errors gracefully with console logging
88-112: LGTM! Media element processing correctly handles both direct src and nested sources.The logic properly:
- Processes the element's own
srcattribute- Iterates through child
<source>elements- Calls
element.load()only when sources were processed but the element's own src was not (line 109-111)The asynchronous processing without
awaitis intentional, allowing conversions to happen in the background while the page continues loading.
121-155: LGTM! MutationObserver implementation is comprehensive and correct.The observer properly handles:
- Newly added media elements (both direct additions and within added subtrees)
srcattribute changes on both media and source elements- Reprocessing by removing elements from the
WeakSetwhen theirsrcchanges (lines 137-138, 143-144)The attribute filter (
['src']) optimizes performance by only observing relevant changes.
157-166: LGTM! Initialization properly handles both loading and loaded states.The code correctly:
- Scans existing media elements if the document has already loaded
- Defers scanning to
DOMContentLoadedif still loading- Sets up the
MutationObserverimmediately to catch dynamically added elementsv3/internal/assetserver/assetserver.go (1)
114-118: LGTM! Internal endpoint routing is correctly implemented.The prefix check ensures
/wails/*requests are handled before user handlers, and the path stripping correctly produces paths like/runtime.jsfor the switch statement.v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts (1)
73-89: LGTM! Platform JS loading flow is well-structured.The asynchronous loading with proper error handling and a
finallyblock ensures the runtime ready signal is always emitted. The script injection viatextContentexecutes synchronously, so core initialization completes before the ready signal.Note: Script execution errors (syntax/runtime errors in platform.js) won't be caught by the
.catch()block—they appear as browser console errors. This is appropriate for development visibility, given that platform.js is an optional enhancement.
| async function convertToBlob(url) { | ||
| const absoluteUrl = toAbsoluteUrl(url); | ||
| if (ENABLE_CACHING && blobUrlCache.has(absoluteUrl)) { | ||
| return blobUrlCache.get(absoluteUrl); | ||
| } | ||
| const response = await fetch(absoluteUrl); | ||
| if (!response.ok) { | ||
| throw new Error('Failed to fetch media: ' + response.status + ' ' + response.statusText); | ||
| } | ||
| const blob = await response.blob(); | ||
| const blobUrl = URL.createObjectURL(blob); | ||
| if (ENABLE_CACHING) { | ||
| blobUrlCache.set(absoluteUrl, blobUrl); | ||
| } | ||
| return blobUrl; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider blob URL lifecycle management to prevent memory leaks.
The blob URLs created via URL.createObjectURL() are never revoked, which can lead to memory accumulation in long-running applications with many media files. While caching mitigates this by reusing URLs, the blobs remain in memory until page reload.
For applications with frequent media changes (e.g., playlists), consider:
- Revoking on element cleanup: Track which elements use which blob URLs and revoke when elements are removed.
- LRU cache with size limit: Revoke oldest blob URLs when cache exceeds a threshold.
For typical applications with a fixed set of media files, the current implementation is acceptable.
Example cleanup approach:
// Track blob URLs per element for cleanup
const elementToBlobUrl = new WeakMap();
// In processMediaElement/processSourceElement, after setting src:
elementToBlobUrl.set(element, blobUrl);
// In MutationObserver, revoke when elements are removed:
for (const node of mutation.removedNodes) {
if (node instanceof HTMLMediaElement || node instanceof HTMLSourceElement) {
const blobUrl = elementToBlobUrl.get(node);
if (blobUrl && blobUrl.startsWith('blob:')) {
URL.revokeObjectURL(blobUrl);
}
}
}🤖 Prompt for AI Agents
In v3/internal/assetserver/assetserver_linux.js around lines 52 to 67, blob URLs
created with URL.createObjectURL are never revoked causing memory leaks in
long-running apps; update convertToBlob or surrounding logic to revoke unused
blob URLs: implement lifecycle management by tracking which element received
which blob URL (e.g., WeakMap element -> blobUrl) and revoke via
URL.revokeObjectURL when the element is removed, and/or replace the simple cache
with an LRU cache that, when exceeding a configured size, calls
URL.revokeObjectURL on the evicted blob URLs before removing them from
blobUrlCache; ensure revoked URLs are removed from the cache and any element
mappings.


Summary
On Linux, WebKitGTK uses GStreamer for media playback. GStreamer doesn't have a URI handler for the
wails://protocol, causing<video>and<audio>elements to fail loading from bundled assets. This is a known upstream WebKit issue that has been open since 2015.This PR adds an automatic workaround that:
wails://URLs to blob URLsfetch()(which works via WebKit's URI scheme handler)Changes
/wails/platform.jsendpoint/wails/*route handling fromBundledAssetServerto mainAssetServerso it works regardless of user's asset handler choiceDisableGStreamerFixandEnableGStreamerCachingoptions toLinuxOptions/wails/platform.jsbefore emitting ready eventaudio-videoexample demonstrating media playback with npm runtimeConfiguration
Test plan
Fixes #4412
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.