John Waring
John Waring

Reputation: 31

Blazor and the Superpowered Web Audio SDK, how to return the arrayBuffer to C#

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.

Upvotes: 1

Views: 815

Answers (1)

John Waring
John Waring

Reputation: 31

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);
    });
}

Upvotes: 2

Related Questions