0

First of all, apologies for such an open question. I think this question comes from a lack of understanding of WASM and the nature of JSInterop in .Net.

I just wanted to reach out to anyone who has used the Superpowered Web Audio SDK with Blazor and find out how they have used it as I am struggling with JSInterop, and I feel like I am making things harder by not using a Javascript based framework.

I've setup a Blazor client web assembly project with a C# class, which acts as an interface to a very simple Javascript file to download and decode an audio file:

public class AudioTest
{
        [Inject]
        IJSRuntime js;

        DotNetObjectReference<AudioTest> objRef;

        protected override void OnInitialized()
        {
            objRef = DotNetObjectReference.Create(this);
        }

        public async void DownloadAndDecode(string url)
        {
            byte[] buffer = await superpoweredModule.InvokeAsync<byte[]>("downloadAndDecode", url, objRef);

            Console.WriteLine(buffer.Length);
        }
       
        [JSInvokable]
        public void ReceiveDecodedBuffer(string url, byte[] buffer)
        {
            Console.WriteLine(buffer);
        }
}

When the C# method DownloadAndDecode() has been called, it passes a reference of itself to the javascript function downloadAndDecode() to be used a call back when the buffer is ready:

import { SuperpoweredTrackLoader } from '/superpowered/SuperpoweredTrackLoaderModule.js';

export async function downloadAndDecode(url, dotNetRef) {
    SuperpoweredTrackLoader.downloadAndDecode(url, async (message) => {
        await dotNetRef.invokeMethodAsync('ReceiveDecodedBuffer', url, message.SuperpoweredLoaded.buffer);
    });
}

However, this results in a runtime error when converting the base64 buffer. I've tried converting the audio buffer from base64 before sending it (which is too slow). I also tried unmarshalled Javascript calls but they only support synchronous calls. Having to convert the buffer when passing between C#, JS and vice versa is too slow.

I planned on processing the audio buffer on both C# and JS. But it feels like I should just leave JS side and manage the buffers there. Does anyone have a suggestion for this? Or should I just leave it in the Javascript side? Or simply change my design/approach and framework to support the Superpowered library.

  • Interesting-looking framework. I've done recording in the past, which involved recording to a buffer, converting to .mp3 in JavaScript, and then uploading through my site's FTP server. I'm very eager to find a good recording solution that works in Blazor, especially now that Apple finally supports the MediaRecorder API. Have you checked out https://stackoverflow.com/questions/64803155/blazor-to-javascript-byte-array-interop/64804675 – Bennyboy1973 Jul 13 '21 at 23:22
  • Yeah I tried using unmarshalled calls to get the audio buffer, but it seems all unmarshalled calls are synchronous, and the Superpowered function 'downloadAndDecode' requires a callback to retrieve the buffer. So any attempt to wait for that call back and send it back wouldn't work. I'll have another try as it does feel like the best approach, I'll try to be a little more creative with using a promise and passing the resolve() function as a callback. – John Waring Jul 14 '21 at 18:31
  • I don't exactly know your use case, but the following seems very promising to me: https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/6.0/byte-array-interop . It's nice to see MS firing on all cylinders on .net progression! – Bennyboy1973 Jul 14 '21 at 20:25
  • Yeah that looks very promising, I had the .NET 6 preview 5 blog open to see if this was a planned change. I'll get it installed and see how it fairs. – John Waring Jul 14 '21 at 21:03
  • That did it. I tried .NET 6 preview 6 on my current project and it acted very strange and eventually errored out when returned the byte[]. But creating a new project based on .net 6 preview 6 and setting it up again and it all works perfectly, so fast as well. Thanks @Bennyboy1973, really appreciate your time! – John Waring Jul 14 '21 at 23:09
  • No problem. I have a question-- is there a free version of that framework? I went to the homepage and saw a bunch of pay tiers. – Bennyboy1973 Jul 14 '21 at 23:12
  • There's an evaluation version you can get, which is what I'm using during prototyping. However, I think the starter license fee is $2,500. – John Waring Jul 14 '21 at 23:37
  • $2,500 you say? For that price, I'm going to build my own library instead. It looks like that package has a big JavaScript component, so I'm thinking it's probably similar to what I already have, but with Blazor interop and some UI stuff. – Bennyboy1973 Jul 15 '21 at 00:34
  • Yeah, I'm thinking the same but wanted to see it in action first. One of the features it has is a WASM memory managed area for the audio buffers, which this google article covers the concept, https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern. Which I suppose is only needed if performance becomes an issue, an interesting approach for the web environment nonetheless. – John Waring Jul 15 '21 at 09:21
  • I feel like the Blazor team needs to get on this, personally. Apple was the last holdout on MediaRecorder, but caniuse shows it supported now. I'm fine with telling Android users to switch to Chrome, but the idea of telling Apple users to switch to Android was much less appealing. :) https://caniuse.com/?search=mediarecorder – Bennyboy1973 Jul 15 '21 at 09:58

1 Answers1

1

Installing .net 6 preview 6 and creating my project again provided a fast and effective way of passing large byte[] between Blazor and JavaScript. Without creating a new project it caused odd behaviour when returning/sending the byte[] .

Here's a sample of my source code. The code requires the Superpowered Web Audio library. ByteArrayTest.razor

<h3>ByteArrayTest</h3>

<button @onclick="SendNetBytes">Send bytes to javascript</button>
<button @onclick="GetJavaBytes">Get bytes from javascript</button>
<button @onclick="DownloadAndDecode">Download And Decode .mp3</button>
<button @onclick="DownloadAndDecodeUsingCallback">Download And Decode .mp3 Using Callback</button>

@code {
    [Inject]
    IJSRuntime jsRuntime { get; set; }
    IJSObjectReference module;
    DotNetObjectReference<ByteArrayTest> objRef;

    protected override async Task OnInitializedAsync()
    {
        objRef = DotNetObjectReference.Create(this);
        module = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "/byteArray.js");
    }

    public async ValueTask DisposeAsync()
    {
        objRef.Dispose();
        await module.DisposeAsync();
    }

    public async void SendNetBytes()
    {
        byte[] bytes = new byte[] { 1, 5, 7 };
        await module.InvokeVoidAsync("getNetBytes", bytes);
    }

    public async void GetJavaBytes()
    {
        byte[] buffer = await module.InvokeAsync<byte[]>("getJavaBytes");
        Console.WriteLine(buffer.Length);
        foreach (byte b in buffer)
        {
            Console.WriteLine(b);
        }
    }

    public async void DownloadAndDecode()
    {
        byte[] buffer = await module.InvokeAsync<byte[]>("downloadAndDecode", "/track.mp3");
        Console.WriteLine(buffer.Length);
        await module.InvokeVoidAsync("getNetBytes", buffer);
    }

    public async void DownloadAndDecodeUsingCallback()
    {
        await module.InvokeVoidAsync("downloadAndDecodeUsingCallback", "/track.mp3", objRef);
    }

    [JSInvokable]
    public async void ReceiveDecodedBuffer(byte[] buffer)
    {
        Console.WriteLine("Got buffer!");
        Console.WriteLine(buffer.Length);
        await module.InvokeVoidAsync("getNetBytes", buffer);
    }
}

wwwroot/byteArray.js

export function getNetBytes(bytes) {
    console.log(bytes);
}

export function getJavaBytes() {
    return new Uint8Array([1, 2, 3, 4, 5]);
}

import { SuperpoweredTrackLoader } from '/superpowered/SuperpoweredTrackLoaderModule.js';

export async function downloadAndDecode(url) {
    const promise = new Promise((resolve) => {
        SuperpoweredTrackLoader.downloadAndDecode(url, async (message) => {
            const buffer = new Uint8Array(message.SuperpoweredLoaded.buffer);
            resolve(buffer);
        });
    });

    const buffer = await promise;
    return buffer;
}

export function downloadAndDecodeUsingCallback(url, dotNetRef) {
    SuperpoweredTrackLoader.downloadAndDecode(url, async (message) => {
        const buffer = new Uint8Array(message.SuperpoweredLoaded.buffer);
        await dotNetRef.invokeMethodAsync('ReceiveDecodedBuffer', buffer);
    });
}