mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
fix(linux): fix video and audio playback on WebKitGTK (#4412)
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 <noreply@anthropic.com>
This commit is contained in:
parent
bf1173c831
commit
cb377cc29f
27 changed files with 1716 additions and 15 deletions
189
docs/src/content/docs/guides/linux-media.mdx
Normal file
189
docs/src/content/docs/guides/linux-media.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: Linux Media Playback
|
||||
sidebar:
|
||||
order: 12
|
||||
---
|
||||
|
||||
import {Badge} from '@astrojs/starlight/components';
|
||||
|
||||
Relevant Platforms: <Badge text="Linux" variant="note" />
|
||||
<br/>
|
||||
|
||||
On Linux, Wails applications use WebKitGTK for rendering web content. WebKitGTK delegates media playback (video and audio) to GStreamer, which handles the actual decoding and rendering of media files.
|
||||
|
||||
## The GStreamer Protocol Issue
|
||||
|
||||
GStreamer operates independently from WebKit's custom URI scheme handling. While Wails registers a `wails://` protocol handler with WebKit for serving your application's assets, GStreamer doesn't have access to this handler. This means when an HTML `<video>` or `<audio>` element tries to load a media file from your bundled assets, GStreamer cannot resolve the `wails://` URL.
|
||||
|
||||
This is a [known upstream WebKit issue](https://bugs.webkit.org/show_bug.cgi?id=146351) that has been open since 2015.
|
||||
|
||||
### Symptoms
|
||||
|
||||
Without the workaround, you might experience:
|
||||
- Video elements showing a black screen or failing to load
|
||||
- Audio elements remaining silent
|
||||
- Browser console errors about failed media requests
|
||||
- Media elements stuck in a "loading" state
|
||||
|
||||
## Automatic Workaround
|
||||
|
||||
Wails automatically works around this limitation by injecting JavaScript that intercepts media elements and converts their sources to blob URLs. This happens transparently - you don't need to modify your frontend code.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. When your application loads, Wails injects platform-specific JavaScript
|
||||
2. This JavaScript monitors the DOM for `<video>` and `<audio>` elements
|
||||
3. When a media element is found with a relative or `wails://` URL, the script:
|
||||
- Fetches the media file using the standard `fetch()` API (which works with the Wails asset server)
|
||||
- Creates a blob URL from the response
|
||||
- Replaces the element's `src` with the blob URL
|
||||
4. GStreamer can then play the blob URL normally
|
||||
|
||||
### What Gets Intercepted
|
||||
|
||||
The workaround intercepts:
|
||||
- `<video>` elements with `src` attributes
|
||||
- `<audio>` elements with `src` attributes
|
||||
- `<source>` elements inside `<video>` and `<audio>` elements
|
||||
- Elements added dynamically via JavaScript
|
||||
- Elements with `src` set via JavaScript after page load
|
||||
|
||||
## Configuration Options
|
||||
|
||||
You can configure the GStreamer workaround behavior using `LinuxOptions`:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
Linux: application.LinuxOptions{
|
||||
// Disable the GStreamer workaround entirely
|
||||
DisableGStreamerFix: false,
|
||||
|
||||
// Enable caching of blob URLs for better performance
|
||||
EnableGStreamerCaching: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### DisableGStreamerFix
|
||||
|
||||
Set to `true` to disable the automatic media interception. You might want to do this if:
|
||||
- You're only using external media URLs (http/https)
|
||||
- You're implementing your own media handling solution
|
||||
- You're debugging media-related issues
|
||||
|
||||
```go
|
||||
Linux: application.LinuxOptions{
|
||||
DisableGStreamerFix: true,
|
||||
},
|
||||
```
|
||||
|
||||
### EnableGStreamerCaching
|
||||
|
||||
When enabled, blob URLs are cached so that the same media file doesn't need to be fetched and converted multiple times. This improves performance when:
|
||||
- The same media is played repeatedly
|
||||
- Multiple elements reference the same media file
|
||||
- Media elements are recreated (e.g., in a SPA with component remounting)
|
||||
|
||||
```go
|
||||
Linux: application.LinuxOptions{
|
||||
EnableGStreamerCaching: true,
|
||||
},
|
||||
```
|
||||
|
||||
:::note[Memory Considerations]
|
||||
Enabling caching keeps blob URLs in memory for the lifetime of the page. For applications with many large media files, consider whether the memory trade-off is worthwhile for your use case.
|
||||
:::
|
||||
|
||||
## Working with Media Files
|
||||
|
||||
### Bundled Assets
|
||||
|
||||
Place media files in your frontend's public or assets directory:
|
||||
|
||||
```
|
||||
frontend/
|
||||
public/
|
||||
video.mp4
|
||||
audio.ogg
|
||||
src/
|
||||
main.js
|
||||
```
|
||||
|
||||
Reference them with relative URLs:
|
||||
|
||||
```html
|
||||
<video controls>
|
||||
<source src="/video.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
<audio controls>
|
||||
<source src="/audio.ogg" type="audio/ogg">
|
||||
</audio>
|
||||
```
|
||||
|
||||
### Supported Formats
|
||||
|
||||
The supported media formats depend on your GStreamer installation. Common formats include:
|
||||
- Video: MP4 (H.264), WebM (VP8/VP9), OGG (Theora)
|
||||
- Audio: MP3, OGG (Vorbis), WAV, FLAC
|
||||
|
||||
To check available codecs on your system:
|
||||
|
||||
```bash
|
||||
gst-inspect-1.0 --version
|
||||
gst-inspect-1.0 | grep -i decoder
|
||||
```
|
||||
|
||||
### Installing Codecs
|
||||
|
||||
On Ubuntu/Debian:
|
||||
```bash
|
||||
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly
|
||||
```
|
||||
|
||||
On Fedora:
|
||||
```bash
|
||||
sudo dnf install gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-plugins-ugly-free
|
||||
```
|
||||
|
||||
On Arch Linux:
|
||||
```bash
|
||||
sudo pacman -S gst-plugins-good gst-plugins-bad gst-plugins-ugly
|
||||
```
|
||||
|
||||
## Example Application
|
||||
|
||||
See the [audio-video example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/audio-video) for a complete working example that demonstrates:
|
||||
- Video playback with bundled MP4 files
|
||||
- Audio playback with bundled MP3 files
|
||||
- Using the `@wailsio/runtime` npm module
|
||||
- Proper project structure for media assets
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Media Not Playing
|
||||
|
||||
1. **Check GStreamer installation**: Ensure GStreamer and required plugins are installed
|
||||
2. **Check the console**: Look for JavaScript errors or failed network requests
|
||||
3. **Verify file paths**: Ensure media files are in the correct location and accessible
|
||||
4. **Check codec support**: Verify GStreamer has the necessary decoder for your media format
|
||||
|
||||
### Performance Issues
|
||||
|
||||
If media playback is sluggish:
|
||||
1. Enable caching with `EnableGStreamerCaching: true`
|
||||
2. Consider using more efficient codecs (e.g., H.264 instead of VP9)
|
||||
3. Reduce media file sizes through compression
|
||||
|
||||
### Debugging
|
||||
|
||||
To see what the media interceptor is doing, check the browser console. The interceptor logs its actions when media sources are converted.
|
||||
|
||||
You can also temporarily disable the fix to compare behavior:
|
||||
|
||||
```go
|
||||
Linux: application.LinuxOptions{
|
||||
DisableGStreamerFix: true,
|
||||
},
|
||||
```
|
||||
|
|
@ -144,18 +144,33 @@ If `wails3 doctor` passes, you're done. [Skip to First App →](/quick-start/fir
|
|||
sudo apt update
|
||||
sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev
|
||||
```
|
||||
|
||||
**For audio/video playback** (optional but recommended):
|
||||
```bash
|
||||
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Fedora">
|
||||
```bash
|
||||
sudo dnf install gcc pkg-config gtk3-devel webkit2gtk4.0-devel
|
||||
```
|
||||
|
||||
**For audio/video playback** (optional but recommended):
|
||||
```bash
|
||||
sudo dnf install gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-plugins-ugly-free
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Arch">
|
||||
```bash
|
||||
sudo pacman -S base-devel gtk3 webkit2gtk
|
||||
```
|
||||
|
||||
**For audio/video playback** (optional but recommended):
|
||||
```bash
|
||||
sudo pacman -S gst-plugins-good gst-plugins-bad gst-plugins-ugly
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Other">
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@ After processing, the content will be moved to the main changelog and this file
|
|||
-->
|
||||
|
||||
## Added
|
||||
- Add `DisableGStreamerFix` and `EnableGStreamerCaching` options to `LinuxOptions` for controlling media playback workaround (#4412) by @leaanthony
|
||||
- Add `audio-video` example demonstrating HTML5 media playback with the `@wailsio/runtime` npm module by @leaanthony
|
||||
<!-- New features, capabilities, or enhancements -->
|
||||
|
||||
## Changed
|
||||
<!-- Changes in existing functionality -->
|
||||
|
||||
## Fixed
|
||||
- Fix video and audio playback on Linux/WebKitGTK by intercepting media elements and converting wails:// URLs to blob URLs (#4412) by @leaanthony
|
||||
<!-- Bug fixes -->
|
||||
|
||||
## Deprecated
|
||||
|
|
|
|||
38
v3/examples/audio-video/README.md
Normal file
38
v3/examples/audio-video/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Audio/Video Example
|
||||
|
||||
This example demonstrates HTML5 audio and video playback using the `@wailsio/runtime` npm module.
|
||||
|
||||
## Linux Notes
|
||||
|
||||
On Linux, WebKitGTK uses GStreamer for media playback. GStreamer doesn't have a URI handler for the `wails://` protocol, which means media files served from the bundled assets won't play directly.
|
||||
|
||||
Wails automatically works around this limitation by intercepting media elements and converting their sources to blob URLs. This happens transparently - you don't need to change your code.
|
||||
|
||||
See the [Linux-specific documentation](https://wails.io/docs/guides/linux-media) for details on:
|
||||
- How the GStreamer workaround works
|
||||
- How to disable it if needed (`DisableGStreamerFix`)
|
||||
- How to enable caching for better performance (`EnableGStreamerCaching`)
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
cd ..
|
||||
go build
|
||||
./audio-video
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
For development with hot-reload:
|
||||
|
||||
```bash
|
||||
# Terminal 1: Run Vite dev server
|
||||
cd frontend
|
||||
npm run dev
|
||||
|
||||
# Terminal 2: Run Go app with dev server URL
|
||||
FRONTEND_DEVSERVER_URL=http://localhost:5173 go run .
|
||||
```
|
||||
6
v3/examples/audio-video/frontend/dist/assets/index-DxRb0nj9.js
vendored
Normal file
6
v3/examples/audio-video/frontend/dist/assets/index-DxRb0nj9.js
vendored
Normal file
File diff suppressed because one or more lines are too long
61
v3/examples/audio-video/frontend/dist/index.html
vendored
Normal file
61
v3/examples/audio-video/frontend/dist/index.html
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audio/Video Example</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { text-align: center; margin-bottom: 20px; color: #00d4ff; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.section h2 { margin-bottom: 15px; color: #00d4ff; }
|
||||
video, audio { width: 100%; border-radius: 8px; background: #000; }
|
||||
video { max-height: 400px; }
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.status.success { background: rgba(0, 255, 0, 0.2); color: #66ff66; }
|
||||
.status.error { background: rgba(255, 0, 0, 0.2); color: #ff6666; }
|
||||
.status.pending { background: rgba(255, 255, 0, 0.2); color: #ffff66; }
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-DxRb0nj9.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Audio/Video Example</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2>Video</h2>
|
||||
<video id="video" controls>
|
||||
<source src="/wails.mp4" type="video/mp4">
|
||||
</video>
|
||||
<div id="video-status" class="status pending">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Audio</h2>
|
||||
<audio id="audio" controls>
|
||||
<source src="/wails.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
<div id="audio-status" class="status pending">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
v3/examples/audio-video/frontend/dist/wails.mp3
vendored
Normal file
BIN
v3/examples/audio-video/frontend/dist/wails.mp3
vendored
Normal file
Binary file not shown.
BIN
v3/examples/audio-video/frontend/dist/wails.mp4
vendored
Normal file
BIN
v3/examples/audio-video/frontend/dist/wails.mp4
vendored
Normal file
Binary file not shown.
61
v3/examples/audio-video/frontend/index.html
Normal file
61
v3/examples/audio-video/frontend/index.html
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audio/Video Example</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { text-align: center; margin-bottom: 20px; color: #00d4ff; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.section h2 { margin-bottom: 15px; color: #00d4ff; }
|
||||
video, audio { width: 100%; border-radius: 8px; background: #000; }
|
||||
video { max-height: 400px; }
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.status.success { background: rgba(0, 255, 0, 0.2); color: #66ff66; }
|
||||
.status.error { background: rgba(255, 0, 0, 0.2); color: #ff6666; }
|
||||
.status.pending { background: rgba(255, 255, 0, 0.2); color: #ffff66; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Audio/Video Example</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2>Video</h2>
|
||||
<video id="video" controls>
|
||||
<source src="/wails.mp4" type="video/mp4">
|
||||
</video>
|
||||
<div id="video-status" class="status pending">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Audio</h2>
|
||||
<audio id="audio" controls>
|
||||
<source src="/wails.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
<div id="audio-status" class="status pending">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
965
v3/examples/audio-video/frontend/package-lock.json
generated
Normal file
965
v3/examples/audio-video/frontend/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,965 @@
|
|||
{
|
||||
"name": "audio-video-example",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audio-video-example",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@wailsio/runtime": "file:../../../internal/runtime/desktop/@wailsio/runtime"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"../../../internal/runtime/desktop/@wailsio/runtime": {
|
||||
"version": "3.0.0-alpha.75",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"happy-dom": "^17.1.1",
|
||||
"promises-aplus-tests": "2.1.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"typedoc": "^0.27.7",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typedoc-plugin-mdn-links": "^4.0.13",
|
||||
"typedoc-plugin-missing-exports": "^3.1.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^5.2.0",
|
||||
"vitest": "^3.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
|
||||
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
|
||||
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
|
||||
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
|
||||
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
|
||||
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wailsio/runtime": {
|
||||
"resolved": "../../../internal/runtime/desktop/@wailsio/runtime",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
|
||||
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.53.3",
|
||||
"@rollup/rollup-android-arm64": "4.53.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.53.3",
|
||||
"@rollup/rollup-darwin-x64": "4.53.3",
|
||||
"@rollup/rollup-freebsd-arm64": "4.53.3",
|
||||
"@rollup/rollup-freebsd-x64": "4.53.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.53.3",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.53.3",
|
||||
"@rollup/rollup-openharmony-arm64": "4.53.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.53.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.53.3",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
v3/examples/audio-video/frontend/package.json
Normal file
16
v3/examples/audio-video/frontend/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "audio-video-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wailsio/runtime": "file:../../../internal/runtime/desktop/@wailsio/runtime"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
BIN
v3/examples/audio-video/frontend/public/wails.mp3
Normal file
BIN
v3/examples/audio-video/frontend/public/wails.mp3
Normal file
Binary file not shown.
BIN
v3/examples/audio-video/frontend/public/wails.mp4
Normal file
BIN
v3/examples/audio-video/frontend/public/wails.mp4
Normal file
Binary file not shown.
40
v3/examples/audio-video/frontend/src/main.js
Normal file
40
v3/examples/audio-video/frontend/src/main.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Import the Wails runtime from npm
|
||||
import '@wailsio/runtime';
|
||||
|
||||
// Setup media status listeners
|
||||
const video = document.getElementById('video');
|
||||
const videoStatus = document.getElementById('video-status');
|
||||
|
||||
video.addEventListener('loadeddata', () => {
|
||||
videoStatus.className = 'status success';
|
||||
videoStatus.textContent = 'Loaded (' + video.duration.toFixed(1) + 's)';
|
||||
});
|
||||
|
||||
video.addEventListener('error', () => {
|
||||
videoStatus.className = 'status error';
|
||||
videoStatus.textContent = 'Failed to load';
|
||||
});
|
||||
|
||||
const audio = document.getElementById('audio');
|
||||
const audioStatus = document.getElementById('audio-status');
|
||||
|
||||
audio.addEventListener('loadeddata', () => {
|
||||
audioStatus.className = 'status success';
|
||||
audioStatus.textContent = 'Loaded (' + audio.duration.toFixed(1) + 's)';
|
||||
});
|
||||
|
||||
audio.addEventListener('error', () => {
|
||||
audioStatus.className = 'status error';
|
||||
audioStatus.textContent = 'Failed to load';
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (videoStatus.classList.contains('pending')) {
|
||||
videoStatus.className = 'status error';
|
||||
videoStatus.textContent = 'Timeout';
|
||||
}
|
||||
if (audioStatus.classList.contains('pending')) {
|
||||
audioStatus.className = 'status error';
|
||||
audioStatus.textContent = 'Timeout';
|
||||
}
|
||||
}, 5000);
|
||||
8
v3/examples/audio-video/frontend/vite.config.js
Normal file
8
v3/examples/audio-video/frontend/vite.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true,
|
||||
}
|
||||
})
|
||||
35
v3/examples/audio-video/main.go
Normal file
35
v3/examples/audio-video/main.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist/*
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Audio/Video Example",
|
||||
Description: "A demo of HTML5 Audio/Video with the Wails runtime npm module",
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.BundledAssetFileServer(assets),
|
||||
},
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Audio/Video Example",
|
||||
Width: 900,
|
||||
Height: 700,
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver/bundledassets"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -15,6 +18,19 @@ const (
|
|||
HeaderAcceptLanguage = "accept-language"
|
||||
)
|
||||
|
||||
// Platform-specific options set during application initialization.
|
||||
var (
|
||||
disableGStreamerFix bool
|
||||
enableGStreamerCaching bool
|
||||
)
|
||||
|
||||
// SetGStreamerOptions configures GStreamer workaround options on Linux.
|
||||
// This is called during application initialization.
|
||||
func SetGStreamerOptions(disable, enableCaching bool) {
|
||||
disableGStreamerFix = disable
|
||||
enableGStreamerCaching = enableCaching
|
||||
}
|
||||
|
||||
type RuntimeHandler interface {
|
||||
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
|
@ -94,6 +110,13 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
|
|||
//}
|
||||
|
||||
reqPath := req.URL.Path
|
||||
|
||||
// Handle internal Wails endpoints - these work regardless of user's handler
|
||||
if strings.HasPrefix(reqPath, "/wails/") {
|
||||
a.serveWailsEndpoint(rw, reqPath[6:]) // Strip "/wails" prefix
|
||||
return
|
||||
}
|
||||
|
||||
switch reqPath {
|
||||
case "", "/", "/index.html":
|
||||
// Cache the accept-language header
|
||||
|
|
@ -133,6 +156,40 @@ func (a *AssetServer) AttachServiceHandler(route string, handler http.Handler) {
|
|||
a.services = append(a.services, service{route, handler})
|
||||
}
|
||||
|
||||
// serveWailsEndpoint handles internal /wails/* endpoints.
|
||||
func (a *AssetServer) serveWailsEndpoint(rw http.ResponseWriter, path string) {
|
||||
rw.Header().Set(HeaderContentType, "application/javascript")
|
||||
switch path {
|
||||
case "/runtime.js":
|
||||
rw.Write(bundledassets.RuntimeJS)
|
||||
case "/platform.js":
|
||||
rw.Write(getPlatformJS())
|
||||
default:
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// getPlatformJS returns the platform-specific JavaScript based on current options.
|
||||
func getPlatformJS() []byte {
|
||||
if platformJS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the fix is disabled, return empty
|
||||
if disableGStreamerFix {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If caching is not enabled, modify the JS to disable caching
|
||||
if !enableGStreamerCaching {
|
||||
return bytes.ReplaceAll(platformJS,
|
||||
[]byte("const ENABLE_CACHING = true;"),
|
||||
[]byte("const ENABLE_CACHING = false;"))
|
||||
}
|
||||
|
||||
return platformJS
|
||||
}
|
||||
|
||||
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
|
||||
err := ServeFile(rw, filename, blob)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -10,3 +10,6 @@ var baseURL = url.URL{
|
|||
Scheme: "https",
|
||||
Host: "wails.localhost",
|
||||
}
|
||||
|
||||
// platformJS is empty on android - no platform-specific JS needed.
|
||||
var platformJS []byte
|
||||
|
|
|
|||
|
|
@ -8,3 +8,6 @@ var baseURL = url.URL{
|
|||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
|
||||
// platformJS is empty on darwin - no platform-specific JS needed.
|
||||
var platformJS []byte
|
||||
|
|
|
|||
|
|
@ -8,3 +8,6 @@ var baseURL = url.URL{
|
|||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
|
||||
// platformJS is empty on ios - no platform-specific JS needed.
|
||||
var platformJS []byte
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
import (
|
||||
_ "embed"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var baseURL = url.URL{
|
||||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
|
||||
//go:embed assetserver_linux.js
|
||||
var platformJS []byte
|
||||
|
|
|
|||
166
v3/internal/assetserver/assetserver_linux.js
Normal file
166
v3/internal/assetserver/assetserver_linux.js
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Wails Linux Media Interceptor
|
||||
*
|
||||
* On Linux, WebKitGTK uses GStreamer for media playback. GStreamer does not
|
||||
* have a URI handler for the "wails://" protocol, causing video and audio
|
||||
* elements to fail loading.
|
||||
*
|
||||
* This script intercepts media elements and converts wails:// URLs to blob URLs
|
||||
* by fetching the content through fetch() (which works via WebKit's URI scheme
|
||||
* handler) and creating object URLs.
|
||||
*
|
||||
* See: https://github.com/wailsapp/wails/issues/4412
|
||||
* See: https://bugs.webkit.org/show_bug.cgi?id=146351
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// This constant is replaced by the server based on EnableGStreamerCaching option
|
||||
const ENABLE_CACHING = true;
|
||||
|
||||
const blobUrlCache = ENABLE_CACHING ? new Map() : null;
|
||||
const processingElements = new WeakSet();
|
||||
const processingSourceElements = new WeakSet();
|
||||
|
||||
function shouldInterceptUrl(src) {
|
||||
if (!src || src.startsWith('blob:') || src.startsWith('data:')) {
|
||||
return false;
|
||||
}
|
||||
if (src.startsWith('wails://')) {
|
||||
return true;
|
||||
}
|
||||
if (src.startsWith('/') || (!src.includes('://') && !src.startsWith('//'))) {
|
||||
return true;
|
||||
}
|
||||
if (src.startsWith(window.location.origin)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function processSourceElement(source) {
|
||||
if (processingSourceElements.has(source)) {
|
||||
return;
|
||||
}
|
||||
const src = source.src || source.getAttribute('src');
|
||||
if (!src || !shouldInterceptUrl(src)) {
|
||||
return;
|
||||
}
|
||||
processingSourceElements.add(source);
|
||||
try {
|
||||
source.dataset.wailsOriginalSrc = src;
|
||||
const blobUrl = await convertToBlob(src);
|
||||
source.src = blobUrl;
|
||||
console.debug('[Wails] Converted source element:', src);
|
||||
} catch (err) {
|
||||
console.error('[Wails] Failed to convert source element:', src, err);
|
||||
}
|
||||
}
|
||||
|
||||
async function processMediaElement(element) {
|
||||
if (processingElements.has(element)) {
|
||||
return;
|
||||
}
|
||||
const src = element.src || element.getAttribute('src');
|
||||
if (src && shouldInterceptUrl(src)) {
|
||||
processingElements.add(element);
|
||||
try {
|
||||
element.dataset.wailsOriginalSrc = src;
|
||||
const blobUrl = await convertToBlob(src);
|
||||
element.src = blobUrl;
|
||||
console.debug('[Wails] Converted media element:', src);
|
||||
} catch (err) {
|
||||
console.error('[Wails] Failed to convert media element:', src, err);
|
||||
}
|
||||
}
|
||||
|
||||
const sources = element.querySelectorAll('source');
|
||||
for (const source of sources) {
|
||||
await processSourceElement(source);
|
||||
}
|
||||
if (sources.length > 0 && element.dataset.wailsOriginalSrc === undefined) {
|
||||
element.load();
|
||||
}
|
||||
}
|
||||
|
||||
function scanForMediaElements() {
|
||||
const mediaElements = document.querySelectorAll('video, audio');
|
||||
mediaElements.forEach(function(element) {
|
||||
processMediaElement(element);
|
||||
});
|
||||
}
|
||||
|
||||
function setupMutationObserver() {
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node instanceof HTMLMediaElement) {
|
||||
processMediaElement(node);
|
||||
} else if (node instanceof Element) {
|
||||
const mediaElements = node.querySelectorAll('video, audio');
|
||||
mediaElements.forEach(function(el) {
|
||||
processMediaElement(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'src' &&
|
||||
mutation.target instanceof HTMLMediaElement) {
|
||||
processingElements.delete(mutation.target);
|
||||
processMediaElement(mutation.target);
|
||||
}
|
||||
if (mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'src' &&
|
||||
mutation.target instanceof HTMLSourceElement) {
|
||||
processingSourceElements.delete(mutation.target);
|
||||
processSourceElement(mutation.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['src']
|
||||
});
|
||||
}
|
||||
|
||||
console.debug('[Wails] Enabling media interceptor for Linux/WebKitGTK');
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', scanForMediaElements);
|
||||
} else {
|
||||
scanForMediaElements();
|
||||
}
|
||||
|
||||
setupMutationObserver();
|
||||
})();
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build windows
|
||||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
|
@ -6,3 +8,6 @@ var baseURL = url.URL{
|
|||
Scheme: "http",
|
||||
Host: "wails.localhost",
|
||||
}
|
||||
|
||||
// platformJS is empty on windows - no platform-specific JS needed.
|
||||
var platformJS []byte
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package assetserver
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver/bundledassets"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BundledAssetServer struct {
|
||||
|
|
@ -18,16 +16,5 @@ func NewBundledAssetFileServer(fs fs.FS) *BundledAssetServer {
|
|||
}
|
||||
|
||||
func (b *BundledAssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, "/wails/") {
|
||||
// Strip the /wails prefix
|
||||
req.URL.Path = req.URL.Path[6:]
|
||||
switch req.URL.Path {
|
||||
case "/runtime.js":
|
||||
rw.Header().Set("Content-Type", "application/javascript")
|
||||
rw.Write([]byte(bundledassets.RuntimeJS))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
b.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,4 +70,20 @@ window._wails.invoke = System.invoke;
|
|||
// Binding ensures 'this' correctly refers to the current window instance
|
||||
window._wails.handlePlatformFileDrop = Window.HandlePlatformFileDrop.bind(Window);
|
||||
|
||||
System.invoke("wails:runtime:ready");
|
||||
// Load platform-specific code before signaling ready
|
||||
// This allows the backend to inject OS-specific fixes (e.g., Linux media interceptor)
|
||||
fetch("/wails/platform.js")
|
||||
.then(response => response.text())
|
||||
.then(code => {
|
||||
if (code && code.trim().length > 0) {
|
||||
const script = document.createElement("script");
|
||||
script.textContent = code;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.debug("[Wails] No platform-specific code to load:", err);
|
||||
})
|
||||
.finally(() => {
|
||||
System.invoke("wails:runtime:ready");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver"
|
||||
"github.com/wailsapp/wails/v3/internal/operatingsystem"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
|
@ -285,6 +286,12 @@ func newPlatformApp(parent *App) *linuxApp {
|
|||
setProgramName(parent.options.Linux.ProgramName)
|
||||
}
|
||||
|
||||
// Configure GStreamer workaround options for the asset server
|
||||
assetserver.SetGStreamerOptions(
|
||||
parent.options.Linux.DisableGStreamerFix,
|
||||
parent.options.Linux.EnableGStreamerCaching,
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,6 +239,17 @@ type LinuxOptions struct {
|
|||
//
|
||||
//[see the docs]: https://docs.gtk.org/glib/func.set_prgname.html
|
||||
ProgramName string
|
||||
|
||||
// DisableGStreamerFix disables the workaround for GStreamer not supporting wails:// URLs.
|
||||
// When false (default), video and audio elements with wails:// URLs are automatically
|
||||
// converted to blob URLs via fetch() to enable media playback on WebKitGTK.
|
||||
// See: https://bugs.webkit.org/show_bug.cgi?id=146351
|
||||
DisableGStreamerFix bool
|
||||
|
||||
// EnableGStreamerCaching enables caching of blob URLs for the GStreamer fix.
|
||||
// When true, blob URLs are cached to avoid re-fetching the same media.
|
||||
// Default is false since the data is already in memory on the server side.
|
||||
EnableGStreamerCaching bool
|
||||
}
|
||||
|
||||
/********* iOS Options *********/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue