Search code examples
blazorblazor-server-side

Pass large JS blob to Blazor byte[]


I need to record some audio, maybe even video, using Media API in JS, example for Blazor. Then, I would like to pass recorded Blob content from JS to Blazor. As far as this is audio or video content, it can be pretty big.

What I have tried so far

  1. Encoding data as ANSI string or passing an array of integers. This results in InvalidDataException, SignalR disconnect, timeout, after a minute or so SignalR gets back to life and C# receives null

  2. Encoding data as base 64 or passing UInt8Array. Outcome is the same.

  3. Passing Blob, ArrayBuffer, or FormData directly from JS. This results in empty object ValueKind: {} in C#.

The source of JS call

recorder.stop();
recorder.exportWAV(async blob => {
  const content = await (new Response(blob).arrayBuffer());
  const contentNums = new Uint8Array(content);
  const contentCodes = new TextDecoder('windows-1252').decode(contentNums);
  const data = new FormData();
  data.append('file', blob, 'Demo');
  //success(window.URL.createObjectURL(blob));
  console.log(blob)
  console.log(content)
  console.log(contentNums)
  console.log(contentCodes)
  success(Array.from(contentNums));
})

The source of C# interop call

private IJSRuntime _scriptRuntime = null;

public async Task<dynamic> GetMedia<dynamic>()
{
  return await _scriptRuntime.InvokeAsync<dynamic>('AudioFunctions.GetMediaFile');
}

Is there a way to pass large byte arrays or at least strings from JS to Blazor.NET?


Solution

  • Appears to be, the hack with ANSI encoding works fine, I just needed to increase the size of SignalR messages.

    Startup

    public void ConfigureServices(IServiceCollection services)
    {
      // ... some other services
    
      services.AddSignalR(o =>
      {
        o.EnableDetailedErrors = true;
        o.MaximumReceiveMessageSize = long.MaxValue;
      });
    }
    

    JS interop

    window.InteropFunctions = window.InteropFunctions || {
    
      GetMediaFile: async (classInstance, cssClass) => {
    
        return await new Promise((success, error) => {
    
          const chunks = [];
          const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
    
          // Event handlers for play and stop to detect when data is available
    
          recorder.addEventListener('stop', {
    
            // Encode data as ANSI string - Windows 1252 
    
            const response = new Blob(chunks, { type: 'audio/webm' });
            const content = await (new Response(response).arrayBuffer());
            const contentNums = new Uint8Array(content);
            const contentCodes = new TextDecoder('windows-1252').decode(contentNums);
            const audioControl = document.querySelector('.' + cssClass + ' audio');
    
            // Play audio in HTML 5 control
    
            if (audioControl) {
              audioControl.src = URL.createObjectURL(response);
            }
    
            // Resolve the promise and send data to Blazor.NET
    
            success(contentCodes);
          });
    
          // Grab recorded data
          
          recorder.addEventListener('dataavailable', {
            if (e.data.size > 0) {
              chunks.push(e.data);
            }
          });
          
          // Record for 5 seconds
          
          recorder.start();      
          setTimeout(() => recorder.stop(), 5000);
        });
      }
    }
    

    C# Interop

    private IJSRuntime _scriptRuntime = null;
    
    public async Task<string> GetMediaFile<string>(params object[] inputs)
    {
      return await _scriptRuntime.InvokeAsync<string>("InteropFunctions.GetMediaFile", inputs);
    }
    

    Initiate interop call from .NET

    var instance = DotNetObjectReference.Create(this);
    var source = await _audioCommandInstance.GetMediaFile(instance, "audio-container");
    
    if (string.IsNullOrEmpty(source) == false)
    {
      var audioBytes = Encoder.GetBytes(source);
      
      await File.WriteAllBytesAsync("audio.wav", audioBytes);
    }