0

My mp3 files are stored in a SQL database and loaded as byte[] data into client applications. With Razor Pages, the solution was to use the HTML <audio> element setting the source attribute to a byte array in memory. This was done using src=@Url.Page(). The url returned a FileContentResult and the audio element played the file. I cannot find a solution to play mp3 in Blazor where the source is a byte array. Here is the Razor Pages markup:

<audio controls autobuffer="autobuffer" autoplay="autoplay">
    <source src="@Url.Page("", "Mp3Data", new { rowid = Model.Rowid })" type="audio/mpeg" />
</audio>
robert
  • 133
  • 1
  • 2
  • 7
  • I haven't done this (I doubt anybody else has either). But maybe we can walk through this together. Does your browser console give you an error when you attempt to load this component? What is the `Page` method supposed to do-- does is just form a URL, or does it actually download the page into memory? – Bennyboy1973 Sep 01 '23 at 22:41
  • `Page` is an extension method inside the [`UrlHelper`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routing.urlhelper?view=aspnetcore-7.0) class. It simply generates a URL like you assumed @Bennyboy1973. – Dimitris Maragkos Sep 02 '23 at 08:49
  • And the URL endpoint returns a FileContentResult - new FileContentResult(data, MediaTypes.Mp3) - where data is a byte array retrieved from the database. It works perfectly. I have around 100 mp3 files stored in the database – robert Sep 02 '23 at 09:40
  • There is no error because I haven't found a way to assign a URL to the src attribute in Blazor. Url.Page() is not supported in Blazor as far as I can see. – robert Sep 02 '23 at 09:43
  • Simply hardcode the URL that points to your endpoint that returns FileContentResult? `src="@($"api/mp3endpoint/{id}")"` – Dimitris Maragkos Sep 02 '23 at 10:57
  • As Dimitris says, if you have some kind of endpoint that is providing audio data at the page pointed to by `URL.Page()`, why not just build a string that points to that endpoint? `string SrcUrl = @"https://example.com/img/1234";` or whatever? – Bennyboy1973 Sep 02 '23 at 12:15
  • 1
    This doesn't work because the api requires authentication in the request header, so I can't simply inject the api URL. With Razor Pages, this is managed by the application and the audio source URL is an internal endpoint that converts the byte array retrieved from the api into a FileContentResult. Regarding the final comment, communication with the database is done via an api, not a web address. – robert Sep 02 '23 at 15:16
  • If you look at the working example from Razor Pages above, you can see that the URL is internal within the web application. That's what I can't see how to replicate in Blazor. – robert Sep 02 '23 at 15:19

2 Answers2

0

Looks like what you are asking is how to pass an authorization header when using html audio tag. Here you can see this problem also applies to img elements with src attribute that points to a secured endpoint. The solutions suggested in the question I linked can be also applied to your scenario. For example your can pass the access token using a query string parameter in the url. You server can then identify those requests and read the token from the query string. SignalR uses this approach and sends the access token in the query string when using WebSockets or ServerSentEvents. You can see in the documentation how it is handled server side:

builder.Services.AddAuthentication(options =>
{
    // ...
}).AddJwtBearer(options =>
  {
      options.Authority = "Authority URL"; // TODO: Update URL

      options.Events = new JwtBearerEvents
      {
          OnMessageReceived = context =>
          {
              var accessToken = context.Request.Query["access_token"];

              // If the request is for your audio endpoint...
              var path = context.HttpContext.Request.Path;
              if (!string.IsNullOrEmpty(accessToken) &&
                  (path.StartsWithSegments("/api/myMp3Endpoint")))
              {
                  // Read the token out of the query string
                  context.Token = accessToken;
              }
              return Task.CompletedTask;
          }
      };
  });

If for your environment this is considered a security risk then your other option is to use HttpClient to fetch the audio as a byte array, convert it to base64 and embed it to your audio element.

@inject HttpClient Http

@if (!string.IsNullOrEmpty(_base64EncodedAudio))
{
    <audio controls autobuffer="autobuffer" autoplay="autoplay">
        <source src="@($"data:audio/mp3;base64,{_base64EncodedAudio}")" type="audio/mpeg" />
    </audio>
}

@code {
    private string _base64EncodedAudio;

    private async Task GetAudio()
    {
        var audioBytes = await Http.GetByteArrayAsync(audioUrl);                
        _base64EncodedAudio = Convert.ToBase64String(audioBytes);
    }
}

Using HttpClient you can pass whatever authorization headers are needed. Demo

Dimitris Maragkos
  • 8,932
  • 2
  • 8
  • 26
  • My client fetches the audio as a byte array using HttpClient - a non-issue. The conversion to base64 works but it fails as a source for the audio control, at least using the code format you provided. I haven't tried embedding Basic authentication into a url, but I would prefer a solution similar to Razor Pages, where it is not needed. – robert Sep 02 '23 at 16:28
  • Check this demo: https://blazorfiddle.com/s/exx9vcvu – Dimitris Maragkos Sep 02 '23 at 16:44
0

Dimitris, that worked. The only change I needed to make to my markup was to add the condition @if(!string.IsNullOrEmpty(_base64EncodedAudio)). With this added, the Base64 conversion was embedded at the right time and the mp3 byte array played. Thank you.

robert
  • 133
  • 1
  • 2
  • 7