Skip to content

Conversation

@leaanthony
Copy link
Member

@leaanthony leaanthony commented Dec 13, 2025

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:

  • Intercepts media elements and converts wails:// URLs to blob URLs
  • Fetches content through fetch() (which works via WebKit's URI scheme handler)
  • Creates object URLs that GStreamer can play

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 guide
  • Add GStreamer codec dependencies to installation docs

Configuration

app := application.New(application.Options{
    Linux: application.LinuxOptions{
        // Disable the workaround if needed
        DisableGStreamerFix: false,
        // Enable caching of blob URLs for better performance  
        EnableGStreamerCaching: true,
    },
})

Test plan

  • Test video playback on Linux with bundled MP4 file
  • Test audio playback on Linux with bundled MP3 file
  • Verify no impact on macOS/Windows builds
  • Verify works with both bundled runtime and npm module

Fixes #4412

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added audio-video sample application demonstrating HTML5 media playback.
    • Added Linux configuration options to control media playback workaround behavior and caching.
  • Bug Fixes

    • Fixed video and audio playback on Linux by converting media URLs to blob URLs.
  • Documentation

    • Added comprehensive Linux media playback guide with troubleshooting and configuration details.
    • Added audio/video installation instructions for Linux distributions.

✏️ Tip: You can customize this high-level summary in your review settings.

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]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

This PR implements a Linux WebKitGTK media playback workaround by introducing JavaScript-based URL interception that converts wails:// and relative URLs to blob URLs, bypassing GStreamer's lack of support for the wails:// protocol. Changes include asset server modifications for platform-specific script loading, configuration options for the fix, an example app, and documentation updates.

Changes

Cohort / File(s) Summary
Documentation
docs/src/content/docs/guides/linux-media.mdx, docs/src/content/docs/quick-start/installation.mdx, v3/UNRELEASED_CHANGELOG.md
New Linux media playback guide documenting GStreamer workaround, injection mechanism, and configuration options. Added optional GStreamer codec installation commands to quick-start for Ubuntu, Fedora, and Arch. Changelog entries for new DisableGStreamerFix and EnableGStreamerCaching options and audio-video example.
Example App – Audio/Video
v3/examples/audio-video/README.md, v3/examples/audio-video/main.go, v3/examples/audio-video/frontend/index.html, v3/examples/audio-video/frontend/package.json, v3/examples/audio-video/frontend/src/main.js, v3/examples/audio-video/frontend/vite.config.js
New audio-video example app with Go backend embedding frontend assets and Vite-based frontend with HTML5 media player. Frontend includes HTML5 audio/video elements with status indicators, Vue-like state management via event listeners, and timeout-based error detection.
Asset Server – Core & Platform Setup
v3/internal/assetserver/assetserver.go, v3/internal/assetserver/bundled_assetserver.go
Added SetGStreamerOptions function for configuring GStreamer workaround and caching. Added /wails/* endpoint handling (serveWailsEndpoint) for serving /runtime.js and platform-specific /platform.js. Removed special-cased /wails/ handling from bundled asset server; delegated to main handler.
Asset Server – Platform JS Implementations
v3/internal/assetserver/assetserver_linux.go, v3/internal/assetserver/assetserver_linux.js, v3/internal/assetserver/assetserver_darwin.go, v3/internal/assetserver/assetserver_android.go, v3/internal/assetserver/assetserver_ios.go, v3/internal/assetserver/assetserver_windows.go
Added platformJS variable declarations across all platforms. Linux platform includes JavaScript interceptor that converts wails:// URLs to blob URLs with configurable caching. Other platforms have empty platformJS stubs (no platform-specific JS needed).
Runtime & Application Integration
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts, v3/pkg/application/application_linux.go, v3/pkg/application/application_options.go
Runtime now loads and injects /platform.js before signaling readiness. Linux app initialization calls SetGStreamerOptions with LinuxOptions flags. Added DisableGStreamerFix and EnableGStreamerCaching boolean fields to LinuxOptions struct for configuration.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • JavaScript interceptor logic (assetserver_linux.js): Complex URL handling, fetching, blob conversion, and DOM mutation observation with caching state management
  • Platform-specific asset server architecture: Multiple platform files with consistent patterns, but requires understanding the intended flow and coordination with runtime injection
  • Runtime integration point: Asynchronous platform.js loading and injection affects application readiness signal
  • Configuration propagation: Settings flow from LinuxOptions through app initialization to asset server behavior modification

Possibly related PRs

  • Gin support #4128: Modifies assetserver's ServeHTTP and request handling logic; shares the same file refactoring pattern and asset endpoint management approach.

Suggested labels

Bug, Documentation, Linux, runtime, size:M, lgtm

Suggested reviewers

  • atterpac

Poem

🐰 A wails URL caught in WebKit's tight grip,
But behold! A rabbit's clever script
Converts the streams to blobs that flow,
GStreamer now can steal the show! 🎬
With caching swift and options fair,
Linux media plays beyond compare! 🎵

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: video and audio playback on WebKitGTK for Linux, with issue reference (#4412).
Description check ✅ Passed The description comprehensively explains the problem, solution, changes, configuration, and test plan, following the template structure with appropriate sections.
Linked Issues check ✅ Passed The PR fully addresses issue #4412 by implementing the blob URL conversion workaround for GStreamer's lack of wails:// URI handler support on Linux.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing media playback on Linux: platform-specific JS, asset server routing, LinuxOptions configuration, runtime modifications, and supporting documentation/examples.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v3/fix/gstreamer-media

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

Deploying wails with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb377cc
Status:🚫  Build failed.

View logs

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
4.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: initialize platformJS to []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 use gstreamer1-plugins-ugly-free, which is in official Fedora repositories and requires no additional setup. The separate gstreamer1-plugins-ugly package 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 both libwebkit2gtk-4.0-dev and libwebkit2gtk-4.1-dev. The docs currently show only libwebkit2gtk-4.0-dev, which works, but users targeting WebKitGTK 4.1 may prefer libwebkit2gtk-4.1-dev. Consider adding a note: "On Ubuntu 22.04, you can alternatively use libwebkit2gtk-4.1-dev for 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 native URL constructor 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 disableGStreamerFix and enableGStreamerCaching are modified by SetGStreamerOptions and read in getPlatformJS (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 hour

However, 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.ReplaceAll to 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

📥 Commits

Reviewing files that changed from the base of the PR and between bf1173c and cb377cc.

⛔ Files ignored due to path filters (7)
  • v3/examples/audio-video/frontend/dist/assets/index-DxRb0nj9.js is excluded by !**/dist/**
  • v3/examples/audio-video/frontend/dist/index.html is excluded by !**/dist/**
  • v3/examples/audio-video/frontend/dist/wails.mp3 is excluded by !**/dist/**, !**/*.mp3
  • v3/examples/audio-video/frontend/dist/wails.mp4 is excluded by !**/dist/**, !**/*.mp4
  • v3/examples/audio-video/frontend/package-lock.json is excluded by !**/package-lock.json
  • v3/examples/audio-video/frontend/public/wails.mp3 is excluded by !**/*.mp3
  • v3/examples/audio-video/frontend/public/wails.mp4 is 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.json
  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
  • v3/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.md
  • docs/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 name FRONTEND_DEVSERVER_URL matches the Go example code. The README references FRONTEND_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 – platformJS initialization is consistent across all platforms.

All platform implementations (Windows, iOS, Linux, Android, Darwin) use the same uninitialized var platformJS []byte pattern, 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; local file: dependency is correctly configured.

The file:../../../internal/runtime/desktop/@wailsio/runtime reference is already locked in package-lock.json with proper resolution and the "link": true flag, confirming npm resolves it correctly. Vite ^5.0.0 is 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 declare var platformJS []byte as uninitialized (nil). The shared getPlatformJS() function explicitly checks if platformJS == nil to 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 defineConfig with build.outDir: 'dist' and build.emptyOutDir: true. The emptyOutDir setting is redundant (Vite defaults to true when outDir is 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.SetGStreamerOptions properly 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.wailsOriginalSrc for 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 src attribute
  • 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 await is 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)
  • src attribute changes on both media and source elements
  • Reprocessing by removing elements from the WeakSet when their src changes (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 DOMContentLoaded if still loading
  • Sets up the MutationObserver immediately to catch dynamically added elements
v3/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.js for 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 finally block ensures the runtime ready signal is always emitted. The script injection via textContent executes 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.

Comment on lines +52 to +67
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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:

  1. Revoking on element cleanup: Track which elements use which blob URLs and revoke when elements are removed.
  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants