Skip to content

Any plans to support WASM / Browser? #9

Description

@sandreas

Hello,

first of all: Thanks for this great library.

Today I tried to use it with Linux and it worked flawlessly. Unfortunately when using an AvaloniaUI Cross Platform GUI Application and trying to start the WASM / Browser build, there is an error:

Unsupported Operating System: Browser

Since this is a wrapper for miniaudio and miniaudio seems to support WASM builds, I would like to ask, if you plan to integrate support for WASM or if I missed something

miniaudio works on all the major desktop and mobile platforms, including Windows, macOS, Linux, BSD, iOS, Android and Web (via Emscripten).

Examples:

I basically ran all this using the AvaloniaUI xplat example and the SoundFlow example from the readme using a MemoryStream to rule out local file accessing errors:

using var audioEngine = new MiniAudioEngine(44100, Capability.Playback);

// 
var audioBytes = Convert.FromBase64(".....");
var audioStream = new MemoryStream(audioBytes);

// Create a SoundPlayer and load an audio file
var player = new SoundPlayer(new StreamDataProvider(audioStream));

// Add the player to the master mixer
Mixer.Master.AddComponent(player);

// Start playback
player.Play();

Activity

  1. LSXPrime commented on Mar 2, 2025

    @LSXPrime
    Owner

    In theory, support should be available. The reason it wasn't supported at launch is due to a misunderstanding on my part, as I initially misread "Emscripten" as "ECMAScript" when I saw it alongside HTML5, likely due to my declining eyesight. I'll add it to my TODO list, and it should be implemented in the next few commits.

  2. sandreas commented on Mar 2, 2025

    @sandreas
    ContributorAuthor

    I'll add it to my TODO list, and it should be implemented in the next few commits.

    Cool, thank you. Let me know if you need help or testing.

    BTW: I found this article, I don't know if this is helpful, if not, just ignore it :-)

  3. E-Hain commented on Mar 3, 2025

    @E-Hain

    In theory, support should be available. The reason it wasn't supported at launch is due to a misunderstanding on my part, as I initially misread "Emscripten" as "ECMAScript" when I saw it alongside HTML5, likely due to my declining eyesight. I'll add it to my TODO list, and it should be implemented in the next few commits.

    I would love if you added WASM support!

  4. github-actions commented on Mar 18, 2025

    @github-actions

    This issue has been automatically marked as stale because it has not had recent activity.
    It will be closed if no further activity occurs.

    If this issue is still relevant, please leave a comment indicating that you would like it to remain open.
    Thank you for your contributions.

  5. sandreas commented on Mar 18, 2025

    @sandreas
    ContributorAuthor

    +1
    This should not be auto-stale :-)

  6. vchelaru commented on Mar 24, 2025

    @vchelaru

    I'm also interested in using this in the FlatRedBall engine that I maintain. I have been very frustrated with NAudio's lack of updates and similar lack of platforms. If I could get this to work on WASM then this could be the audio solution we use at FRB.

  7. github-actions commented on Apr 8, 2025

    @github-actions

    This issue has been automatically marked as stale because it has not had recent activity.
    It will be closed if no further activity occurs.

    If this issue is still relevant, please leave a comment indicating that you would like it to remain open.
    Thank you for your contributions.

  8. LSXPrime commented on Apr 8, 2025

    @LSXPrime
    Owner

    This shouldn't be marked as stale.

    The WASM support was actually the most problematic issue I worked on, mainly because the JSInterop lacks complex marshalling. And while I have finally tackled that and got compilation done, for some reason, the JSImport attribute and JSHost can't seem to find the exported functions even though I can call them from the browser console.

    I will get busy for the next two weeks, so this will be delayed, sorry.

  9. sandreas commented on Apr 8, 2025

    @sandreas
    ContributorAuthor

    @LSXPrime

    I will get busy for the next two weeks, so this will be delayed, sorry.

    If you need help with the JSHost / JSImport stuff, feel free to reach out. While I'm not an expert, I would recommend to (if possible) NOT use JSHost at all and use globalThis instead.

    globalThis refers to window and you can translate objects and functions without using any of JSHost- so [JSImport("globalThis.localStorage.getItem")] refers to window.localStorage.getItem, which basically means that you can map global functions without JSHost.

    See https://github.com/sandreas/Avalonia.SimplePreferences/blob/335879bb0554a444ef218da53d03f1d17e212829/Avalonia.SimplePreferences/Storages/BrowserStorage.cs#L61 for an example.

    However, since miniaudio WASM probably will spit out a js file which needs to be loaded, you could refer to a more elaborate explanation how to use JSHost, which is burried in the AvaloniaUI discussions containing my notes on how to do it:
    AvaloniaUI/Avalonia#18139 (reply in thread)

    Hope it helps. Good luck!

  10. LSXPrime commented on Apr 9, 2025

    @LSXPrime
    Owner

    Any help would be appreciated. I uploaded my last changes regarding the WASM support to branch wasm-platform. Currently, it loads, but on calling methods like AllocateContext from C# code, which returns a pointer nint/IntPtr, I got this error in the browser console.

    MONO_WASM: Exception marshalling result of JS promise to CS:  RuntimeError: memory access out of bounds
        at mono_class_get_image (dotnet.native.wasm:0x68dd07)
        at get_native_to_interp (dotnet.native.wasm:0x8d7ea4)
        at mono_wasm_get_native_to_interp_trampoline (dotnet.native.wasm:0x754c60)
        at interp_create_method_pointer (dotnet.native.wasm:0x62d363)
        at jit_compile_method_with_opt_cb (dotnet.native.wasm:0x73f143)
        at mono_llvm_cpp_catch_exception (dotnet.native.wasm:0x65175e)
        at jit_compile_method_with_opt (dotnet.native.wasm:0x73f053)
        at mono_jit_compile_method (dotnet.native.wasm:0x73ee76)
        at mono_compile_method_checked (dotnet.native.wasm:0x7056e7)
        at mono_delegate_to_ftnptr_impl (dotnet.native.wasm:0x6e8f0f)
    
    pt @ logging.ts:26
    (anonymous) @ marshal-to-cs.ts:339
    Promise.then
    mo @ marshal-to-cs.ts:329
    (anonymous) @ marshal-to-cs.ts:83
    (anonymous) @ invoke-js.ts:202
    Ll @ invoke-js.ts:276
    $do_icall @ dotnet.native.wasm:0x631112
    $do_icall_wrapper @ dotnet.native.wasm:0x62c057
    $mono_interp_exec_method @ dotnet.native.wasm:0x61df69
    $interp_runtime_invoke @ dotnet.native.wasm:0x62d96c
    $mono_jit_runtime_invoke @ dotnet.native.wasm:0x742c70
    $do_runtime_invoke @ dotnet.native.wasm:0x7034d0
    $mono_runtime_invoke_checked @ dotnet.native.wasm:0x70346a
    $mono_runtime_try_invoke_byrefs @ dotnet.native.wasm:0x709b83
    $ves_icall_InternalInvoke @ dotnet.native.wasm:0x6cee04
    $ves_icall_InternalInvoke_raw @ dotnet.native.wasm:0x6d7c7b
    $do_icall @ dotnet.native.wasm:0x6311cb
    $do_icall_wrapper @ dotnet.native.wasm:0x62c057
    $mono_interp_exec_method @ dotnet.native.wasm:0x61df69
    $interp_runtime_invoke @ dotnet.native.wasm:0x62d96c
    $mono_jit_runtime_invoke @ dotnet.native.wasm:0x742c70
    $do_runtime_invoke @ dotnet.native.wasm:0x7034d0
    $mono_runtime_try_invoke @ dotnet.native.wasm:0x703f50
    $mono_runtime_invoke @ dotnet.native.wasm:0x706acd
    $mono_wasm_invoke_method_bound @ dotnet.native.wasm:0x8d87bf
    (anonymous) @ dotnet.native.js:936
    ccall @ dotnet.native.js:8977
    (anonymous) @ dotnet.native.js:8996
    kr @ invoke-cs.ts:273
    l.javaScriptExports.call_entry_point @ managed-exports.ts:60
    Fc @ run.ts:44
    (anonymous) @ main.js:13
    

    while calling the method window.miniaudioModule._sf_allocate_context directly from browser console just works fine and return this (I assume this is what a pointer seems in javascript),

    (...args) => {
        assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`);
        var f = wasmExports[name];
        assert(f, `exported native function \`${name}\…
    
  11. sandreas commented on Apr 9, 2025

    @sandreas
    ContributorAuthor

    Currently, it loads, but on calling methods like AllocateContext from C# code, which returns a pointer nint/IntPtr, I got this error in the browser console.

    While this may be a problem in your code, it might also be a problem in C# WASM for specific systems (see dotnet/runtime#105893). However, it may take some time until I can take a look - pretty busy right now. Let's hope for the best.

    Here's another interesting link I found: https://stackoverflow.com/questions/57938081/blazor-preview-9-mono-wasm-memory-access-out-of-bounds-max-string-size-for-dotn

    Did you already consult an LLM?

  12. 16 remaining items

  13. LSXPrime commented on Jun 1, 2025

    @LSXPrime
    Owner

    Can this action be configured to check for a specific tag (e.g., 'persisted-issue') before marking an item as stale?

  14. sandreas commented on Jun 2, 2025

    @sandreas
    ContributorAuthor

    Can this action be configured to check for a specific tag (e.g., 'persisted-issue') before marking an item as stale?

    with:
      exempt-issue-labels: 'work-in-progress'
    

    Then label the issue with work-in-progress. See https://github.com/actions/stale?tab=readme-ov-file#exempt-issue-labels

  15. added
    in-progressThis issue is actively being worked on; development is currently in progress.
    on Jun 2, 2025
  16. Axwabo commented on Sep 26, 2025

    @Axwabo

    I would also like this see work!
    Currently, I'm wrapping NAudio with SoundFlow on desktop, and for the browser version I'm using an AudioWorkletNode (web standard) that reads from an NAudio provider.

  17. sandreas commented on Sep 26, 2025

    @sandreas
    ContributorAuthor

    I would also like this see work!

    The problem is: It isn't that easy. I tried (not too hard I admit) but somehow it did not work. I moved on and focused on a native Android App for my project instead of trying to bend C# to something it was not meant for. WASM would have been nice, but I did just not want even more hoops to jump through. Maybe some day I find the time to try again, because I really like this library.

  18. LSXPrime commented on Jan 9, 2026

    @LSXPrime
    Owner

    It has been a long time since the last update. I hadn't made much progress because I thought the issue required JS knowledge (which is something I usually avoid—sorry!). However, since I shipped the latest update yesterday, I didn't want to leave without trying.

    I found that JavaScript marshalling was still limiting us; callbacks refuse to marshal correctly no matter how much I dig in. I won't go into detail as my time is limited, but I discovered that we can actually use a Native approach without interacting with JavaScript at all. If I had realized this earlier, we would have had WASM support a year ago (sorry again)!

    I spent all day trying the JS Interop approach, but a few hours ago I switched to the Native approach. Even though there aren't many resources for it, I’ve made much better progress. It’s not finished yet, and I’m afraid I haven't slept in over 40 hours. I need to get a few hours of sleep now because I have to handle a week's worth of preparations as soon as I wake up. (I think I can put 'works well under pressure' on my resume now, haha). Enough chit-chat, here's the current state

    The Setup

    Everything is pushed to the platform/wasm-browser branch, based on the current master branch (v1.4.0), the old wasm-platform still there but it's outdated.

    1. Native Build:
      The Native\miniaudio-backend\CMakeLists.txt is updated to detect Emscripten. It builds a static library (libminiaudio.a) instead of a shared object, which is required for .NET WASM module to link against. It uses the flags -msimd128 and -fwasm-exceptions to match the .NET 8 runtime.

    2. Managed Build:
      I added SoundFlow.Samples.AvaloniaCrossPlatform to test this. The main SoundFlow project is multi-targeted to include net8.0-browser.

    How to Replicate (The Development Loop)

    To get to the state where you can see the error, you need to do the following:

    1. Run the standard build script: Native\miniaudio-backend\build-wasm\build.ps1
      • This file is configured to use Emscripten bundled with dotnet, modify paths based on your setup.
      • This successfully compiles the C code into libminiaudio.a.
    2. The Manual Step:
      • Usually I would place the libminiaudio.a file in Src\Backends\MiniAudio\runtimes\browser-wasm\native\, but during my debugging loop, I placed it in Samples\SoundFlow.Samples.AvaloniaCrossPlatform\SoundFlow.Samples.AvaloniaCrossPlatform.Browser and configured the browser sample to link it from there to avoid potential paths issues.
      • So, to ensure the build picks it up, I was copying libminiaudio.a directly to the root of the SoundFlow.Samples.AvaloniaCrossPlatform.Browser project folder (I already left a copy there).
    3. Run the SoundFlow.Samples.AvaloniaCrossPlatform.Browser project.

    The Blockade (Current Crash)

    The project builds and launches, but crashes immediately when the audio engine initializes and tries to call into C code.

    Incase you removed the if (!OperatingSystem.IsBrowser()) check at static Native() in Src/Backends/MiniAudio/Native.cs, it will load NativeLibrary.GetMainProgramHandle() and you should get this log in browser console

    The Error:

    logging.ts:26  MONO_WASM: Exception marshalling result of JS promise to CS:  ExitStatusmessage: "Program terminated with exit(1)"name: "ExitStatus"silent: truestack: "Error\n    at Object.Me [as mono_exit] (https://localhost:5001/_framework/dotnet.js:3:18993)\n    at Fl.e.onAbort.e.onAbort (https://localhost:5001/_framework/dotnet.runtime.js:3:215571)\n    at abort (https://localhost:5001/_framework/dotnet.native.js:859:22)\n    at _dlopen (https://localhost:5001/_framework/dotnet.native.js:5296:7)\n    at SystemNative_GetDefaultSearchOrderPseudoHandle (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[27957]:0x8949aa)\n    at do_icall (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[16249]:0x637f86)\n    at do_icall_wrapper (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[16134]:0x632f0a)\n    at mono_interp_exec_method (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[16127]:0x624cf7)\n    at interp_runtime_invoke (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[16167]:0x63481f)\n    at mono_jit_runtime_invoke (https://localhost:5001/_framework/dotnet.native.wasm:wasm-function[20704]:0x749bee)"status: 1[[Prototype]]: Object
    pt @ logging.ts:26
    (anonymous) @ marshal-to-cs.ts:339
    

    What I have tried (See Src/Backends/MiniAudio/Native.cs):

    After I added if (!OperatingSystem.IsBrowser()) to static Native() to prevent resolver from running in browser environment, I have tried every standard variation for the LibraryImport library name string for static linking in Blazor/WASM:

    • [LibraryImport("__Internal")] (The standard way)
    • [LibraryImport("miniaudio")]
    • [LibraryImport("libminiaudio")]
    • [LibraryImport("*")]

    You will see my comment in Native.cs:

    #if BROWSER
        private const string LibraryName = "__Internal"; // Nothing works, not "__Internal" or "*" or "miniaudio"
    #else
        private const string LibraryName = "miniaudio";
    #endif

    None of them work. Throws DllNotFoundException :

    dotnet.native.js:1640 System.DllNotFoundException: miniaudio 
    dotnet.native.js:1640    at SoundFlow.Backends.MiniAudio.MiniAudioEngine.InitializeBackend() in PATH\TO\SoundFlow\Src\Backends\MiniAudio\MiniAudioEngine.cs:line 150
    dotnet.native.js:1640    at SoundFlow.Backends.MiniAudio.MiniAudioEngine..ctor(IEnumerable`1 backendPriority) in PATH\TO\SoundFlow\Src\Backends\MiniAudio\MiniAudioEngine.cs:line 113
    dotnet.native.js:1640    at SoundFlow.Samples.AvaloniaCrossPlatform.ViewModels.MainViewModel.InitializeAudioEngine() in PATH\TO\SoundFlow\Samples\SoundFlow.Samples.AvaloniaCrossPlatform\SoundFlow.Samples.AvaloniaCrossPlatform\ViewModels\MainViewModel.cs:line 51
    

    At this point we need Microsoft to tell us, how to call P/Invokes that got linked from dotnet.native.wasm

  19. sandreas commented on Jan 10, 2026

    @sandreas
    ContributorAuthor

    First I wanna thank you. Not only for making this project happen, but also for putting that much effort in this issue. I hope that you can see it like I always do, when something takes much more effort than I thought it would:

    I learned a lot on the way, so the time was not wasted
    

    I have indeed learned a lot reading your thoughts on this issue. Maybe I throw this into an LLM and see what gets recommended (which you might already have tried).

    Unfortunately I recently made progress in a private project I dreamed about for years and which is kind of my endboss: A tiny DIY portable embedded audio player with touch screen controls (inspired by and to replace my iPod Nano 7g for audio book listening one day). This thing is eating up every single bit of free time ATM (God I currently hate Rust and its learning curve). However, I might take a look at this as soon as I get stuck with the player.

    I wish you all the best for your service and I really hope you return healthy ASAP.

    sandreas

  20. OptimusPi commented on Mar 4, 2026

    @OptimusPi

    Bump, any way I can help?

  21. KieranFoot commented on Apr 19, 2026

    @KieranFoot

    I have started work on fixing this on my own fork.

    I have fixed the imports simply by wrapping the native imports in pre-processor directives to check from browser and using DllImport instead of LibraryImport as shown in the example below.

    #if BROWSER
        [DllImport("libminiaudio", EntryPoint = "ma_encoder_uninit")]
        public static extern void EncoderUninit(nint pEncoder);
    #else
        [LibraryImport(LibraryName, EntryPoint = "ma_encoder_uninit")]
        public static partial void EncoderUninit(nint pEncoder);
    #endif 

    However, now the compiler is complaining about the use of UnmanagedFunctionPointer for OnAudioData in MiniAudioEngine not actually using a function pointer.

    I will update this issue with any further progress.

  22. KieranFoot commented on Apr 19, 2026

    @KieranFoot

    Despite fixing the interop, I now have several todo items to fully implement WASM support.

    See #109

  23. linked a pull request that will close this issuePlatform/wasm browser #109on May 11, 2026
  24. LSXPrime commented on May 11, 2026

    @LSXPrime
    Owner

    Happy to see some progress here. Honestly, after being offline for over 10 years, anything web- or browser-related still throws me off a bit, so I haven’t really touched WebAssembly until this.

    I see a lot of refactoring in this PR. Is updating to .NET 10 strictly necessary? If so, I think it should wait for v2.0. I’m planning to release v2.0 once my current situation settles down.

    I have some points on the PR, will add them to a separate comment there.

  25. KieranFoot commented on Jun 4, 2026

    @KieranFoot

    The update to .NET 10 was mainly for the performance gains in the updated runtime as a whole, so no, not required at all, but a nice addition.

  26. OptimusPi commented on Jun 4, 2026

    @OptimusPi

    The update to .NET 10 was mainly for the performance gains in the updated runtime as a whole, so no, not required at all, but a nice addition.

    Bootsharp.com

    Maybe it'll come in handy

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is neededin-progressThis issue is actively being worked on; development is currently in progress.

    Projects

    • Status
      In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions