Search code examples
blazorsuperpowered

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.


Solution

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