The web platform is now supported, but some testing is welcome. Please note that filters for single sounds are not supported on the web.
To add the plugin to a web app, add the following line to the <body> section of web/index.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.
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:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpAudio 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.
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. TheAudioWorkletbuild mixes on a separate real-time thread, so audio stays smooth no matter how heavy the UI gets. - Future-proof.
ScriptProcessorNodehas been deprecated for years and could be removed from browsers;AudioWorkletis 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:
# 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.dartSo 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 aCross-Origin-Resource-Policy: cross-originheader 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 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:
flutter run -d chrome --web-browser-flag '--disable-web-security' -t lib/main.dart --releaseIt 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.