Serve files embedded in .net library to HTML in Blazor

.net-standard blazor c# embedded-resource javascript

Question

I am creating a Blazor project v0.5.1 that is using a .NET Standard library to provided all of the business logic. This library has several WAV files stored as Embedded Resources.

Consuming one of the resources in a typical .NET technology (WinForms, WPF, etc.) that reference this library can be done with this:

var assemName = "MyLibName";
var assembly = AppDomain
    .CurrentDomain
    .GetAssemblies()
    .First(a => a.GetName().Name == assemName);

//memory stream
var stream = assembly.GetManifestResourceStream($"{assemName}.mysound.wav");

//Can play the file at some point later
var player = new SoundPlayer(stream)
player.Play();

I would like to do the equivalent in Blazor. Right now, I have sounds working in the app by copy/pasting the wav files into wwwroot\sounds folder of the project so it is served as static HTML content. Then, in JavaScript, I can play one like this:

const audio = new Audio"\sounds\mysound.wave"]);
audio.currentTime = 0;
audio.play();

But what I would really like to do is avoid the copy-paste and somehow serve out the files dynamically as those endpoints so it is transparent to JS.

Popular Answer

Well, I was able to get it. Not sure it if the the best way but it seems to work well.

I had to add the blazor Storage extension:

https://github.com/BlazorExtensions/Storage

which acts as a proxy to the JavaScript SessionStorage and LocalStorage from .NET. Once loaded, I added each embedded wav file from .NET like this:

foreach (var kvp in SoundStreamDictionary)
{
    await sessionStorage.SetItem(
        kvp.Key.ToString().ToLower()  //Key is the name of the sound
        , kvp.Value.ToBase64()  //Value is a Stream object
    );
}

The ToBase64 is pretty standard .NET stream manipulation:

    public static string ToBase64(this Stream stream)
    {
        byte[] bytes;
        using (var memoryStream = new MemoryStream())
        {
            stream.Position = 0;
            stream.CopyTo(memoryStream);
            bytes = memoryStream.ToArray();
        }

        return Convert.ToBase64String(bytes);
    }

Now all data is stored in JavaScript SessionStorage as strings. Trick is now to decode that to audio. With the help of this JS helper method (thanks to this StackOverflow post):

function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    const byteCharacters = atob(b64Data);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        const slice = byteCharacters.slice(offset, offset + sliceSize);

        const byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        const byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    return new Blob(byteArrays, { type: contentType });
}

Put that and this in the JsInterop.js file of the BlazorComponent:

const soundAudios = [];

window.JsSound = {

    loadSounds: function (sounds) {
        sounds.forEach(sound => {
            //Blazor.Storage extension adds extra double quotes so trim
            const str64 = sessionStorage.getItem(sound.path).slice(1, -1);

            const blob = b64toBlob(str64, "audio/wav");
            const blobUrl = URL.createObjectURL(blob);

            const audio = new Audio(blobUrl);
            soundAudios[sound.id] = audio;
        });

        return true;
    },

    play: function (id) {
        const audio = soundAudios[id];
        audio.currentTime = 0;
        audio.play();

        return true;
    },
};

And FINALLY the sounds can be invoked by name from .Net:

private IDictionary<string, int> soundDict = new Dictionary<string, int>();

public Task<string> LoadSounds(IEnumerable<string> fileNames)
{
    var sounds = fileNames
        .Select(name =>
        {
            var snd = new
            {
                id = soundDict.Count,
                path = name
            };
            soundDict.Add(name, snd.id);

            return snd;
        })
        .ToList();

    return JSRuntime.Current.InvokeAsync<string>(
        "JsSound.loadSounds"
        , sounds
    );
}

public Task<string> Play(string name)
{
    return JSRuntime.Current.InvokeAsync<string>(
        "JsSound.play"
        , soundDict[name]
    );
}


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow