---
title: Web Notes
description: Learn how to use flutter_soloud on the web
---

## Description

The web platform is now supported, but some testing is welcome.
Please note that filters for single sounds are not supported on the web.

## How to use

To add the plugin to a web app, add the following line to the `<body>` section of `web/index.html`:
```html
<script src="assets/packages/flutter_soloud/web/init_soloud.js" defer></script>
```
This script automatically picks the best WASM build for the current page (see below). The old two-tag form that also loads `libflutter_soloud_plugin.js` explicitly is still supported, but no longer needed.

## Two WASM builds: AudioWorklet when possible

Since version 4.1.8, the plugin ships two WebAssembly builds and chooses between them at runtime:

- **Multi-threaded (AudioWorklet)** — used when the page is **cross-origin isolated**, i.e. served with:
  ```http
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp
  ```
  Audio is rendered on a dedicated real-time `AudioWorklet` thread.
- **Single-threaded** — used everywhere else. Audio is rendered on the main browser thread via the (deprecated) `ScriptProcessorNode`. No special headers are required, so the plugin works on any static hosting, including game portals like CrazyGames or Poki that cannot send COOP/COEP headers.

### Why you should enable COOP/COEP when you can

The multi-threaded build is strictly better whenever the headers are an option:

- **No audio glitches from UI work.** With `ScriptProcessorNode`, mixing happens on the main browser thread — the same thread that runs Flutter's rendering, layout, and the Dart event loop. A busy frame or a GC pause directly starves the audio callback and produces crackles. The `AudioWorklet` build mixes on a separate real-time thread, so audio stays smooth no matter how heavy the UI gets.
- **Future-proof.** `ScriptProcessorNode` has been deprecated for years and could be removed from browsers; `AudioWorklet` is its designated replacement.

If you control your hosting, enable the headers. For local development, which build you get depends on how you run the app:

```bash
# No COOP/COEP headers are sent by the dev server: the page is NOT
# cross-origin isolated and the single-threaded build is used.
flutter run -d chrome -t lib/main.dart

# The dev server sends its own COOP: same-origin + COEP: credentialless
# headers (needed by the WasmGC renderer): the page IS cross-origin
# isolated and the AudioWorklet build is used. Do NOT add --web-header
# flags here — see below.
flutter run -d chrome --wasm -t lib/main.dart

# Manual headers, without --wasm: the flags are applied as-is, the page
# IS cross-origin isolated and the AudioWorklet build is used.
flutter run -d chrome \
  --web-header=Cross-Origin-Opener-Policy=same-origin \
  --web-header=Cross-Origin-Embedder-Policy=require-corp \
  -t lib/main.dart
```

So the `--web-header` flags are only useful (and only safe) **without** `--wasm`. With `--wasm` the dev server already isolates the page on its own, and your flags get *appended* to Flutter's defaults, producing an invalid conflicting header (`COEP: credentialless, require-corp`) on asset responses. That blocks the plugin's worker threads (`ERR_BLOCKED_BY_RESPONSE` in the console) and the WASM module never finishes loading.

Two caveats when enabling them:

- Cross-origin isolation requires a secure context, so deploy over **HTTPS**.
- With `require-corp`, resources loaded from other origins (CDN assets, fonts, third-party APIs) must send a `Cross-Origin-Resource-Policy: cross-origin` header or be fetched with CORS, otherwise the browser blocks them. Also note that COOP/COEP is incompatible with some popup-based flows (e.g. certain Google Auth popups) and with cross-origin iframes used by ad networks — which is exactly why game portals (like CrazyGames and Poki) don't support it.

If the headers are missing, you don't need to do anything: the plugin silently falls back to the single-threaded build.

For release builds, use `flutter build web` or `flutter build web --wasm` — the build command itself is unrelated to the headers; what matters is only whether your server sends them when serving the app.

---

**`loadUrl()`** may produce the following error when the app is run:
>> Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

This is due to the default behavior of HTTP servers which don't allow requests outside their domain. Refer to [this guide](https://enable-cors.org/server.html) to learn how to enable your server to handle this situation.
Instead, if you run the app locally, you could run the app with something like the following command:
```bash
flutter run -d chrome --web-browser-flag '--disable-web-security' -t lib/main.dart --release
```

---

***It is not possible to read a local audio file directly*** on the web. For this reason, `loadMem()` has been added, which requires the `Uint8List` byte buffer of the audio file.
***NOTE***: on the web the `mode` parameter of `loadMem()` is ignored (`LoadMode.disk` is not possible, since browsers cannot read from the local file system). The audio data is instead fed to the engine in chunks, yielding to the event loop between each chunk, so even large files should not freeze the UI.
