FNC WX Audio player question

I'm needing to play a series of rather short audio files. I have this player set up to play, then in the onEndTrack handler (whatever it's called) it loads up the next file, but that's causing a noticeable delay in the audible stream. In the VCL, I can DL the files and stuff them into a TMemoryStream, and keep feeding them in, but all this component accepts is a URL and it does its own loading. I guess this is due to limitations of how it's implemented using the HTML5 Audio tag.

Is there some way to make this pre-load the next track? Or buffer several files together?

Is there any chance you guys can add a Tracks property, like Lines or Items, that lets you give it a list of URLs to load in a sequence, and it manages things in a way that ensures there's no pause between tracks needed when one track finishes and the next one is being downloaded? Unless I'm missing something, I don't know how this can be done with the current design.


We tested this by converting 2 (2-4 seconds) MP3 files to base64 strings and then assigning them to the URL property in the OnEnded event. We could not hear any noticeable delay.

How did you implement the OnEnded event? Could it be you are doing base64 conversion there and that is what takes a long time?

The clips themselves tend to be 10-20 seconds in length.

Right now, the delay is most likely caused by the onEnded handler simply assigning to the URL property.

I'm curious about something: when you DL a binary file like an MP3 file from a remote server, is it going to be sent already encoded as base64 data? Or can you request that somehow?

If so, is there some way to keep it in that format rather than having it converted to MP3 binary and then having to encode it back to base64?

Even a 10 second audio file converted to base64 did not give a noticeable delay here. Does your OnEnded event do anything else other than assigning the URL? Are you doing any file loading or conversion in there?

Most likely not. Some services might have separate or additional endpoints to return the audio files as base64 encoded data. It won't be possible to tell the remote server what format to return unless it supports it.

What is the reason behind not converting it once (from binary to base64) as it arrives then storing it like that? Do you need to save the files as playable .mp3?

You've asked this twice, and I don't know why -- I assign a remote path to the .URL property and the component does the download and whatever else it needs. These things take more than a few milliseconds to actually download unless the file is in a cache somewhere, and normally they will NOT be in this case.

audioplayer1.URL := 'https://somewebsite.com/path/to/file_862765598766145.mp3'

Show me the code you're using.

It's just a question, because I don't understand what process you're using. It's as if the doc says this:

"The player accepts an MP3 file, but only if it's located outside of the browser. If it's inside the browser, it's considered a security risk. However, if you stomp your foot three times, bend over and touch your toes, then pull on your left earlobe twice, we'll let you sneak something in, but only if your MP3 file has been encoded in a totally different format that looks nothing like an MP3 file."

I find it quite confusing.

Components are supposed to make interfacing with them easier, not more complicated.

Why can't you just have another property that lets you hand it an array or list of TStreams that contain MP3 files in them, and let it go through whatever contortions it needs to go through on its own, and skip the dance?

function downloadMP3( const aURL : string ) : TMemoryStream;
. . .
var myStreamArray : array of TMemoryStream;
. . .
SetLength( myStreamArray, 3 );
myStreamArray[0] := downloadMP3( aURL1 ); 
myStreamArray[1] := downloadMP3( aURL2 ); 
myStreamArray[2] := downloadMP3( aURL3 ); 
audioplayer.LoadStreams( myStreamArray );

If there's more than one, then it will simply play them in sequence with no pauses between them.

Sorry for the confusion, up until now it sounded like you were trying to play something that was requested/downloaded from a server beforehand and stored somewhere. So we tested it like that: we got a base64 (string) encoded audio file and assigned that to the URL.

procedure TForm1.TMSFNCWXAudioPlayer1Ended(Sender: TObject);
  TMSFNCWXAudioPlayer1.URL := AUDIO_1; 
  //AUDIO_1 is defined as constant. 
  //The value is a base64 encoded string:

Based on that small snippet you sent it looks like you are using an online URL to play an audio file. Is that the case? Then delay is indeed expected as it depends on your network and on the server how fast it can load and play the file.

Unfortunately there's not much can be done about that other than fetching the files from the server in advance and storing them until they are ready to be played.

This could be an addition to the component but needs research first so we need to find some time where we can do that. There's so much going on with different supported audio formats between browsers (mp3, ogg, wav, and so on...) that it's not possible to tell right now if something like this can be added or not.

Yes. What you showed there is NOT a "URL" but a huge string constant. You should have a separate property for that, IMO.

I actually generate most of the audio on-the-fly on a remote server and I get back URLs that point to MP3 files that need to be downloaded. Some can be saved, but most get discarded fairly quickly.

How do you generate that base64 on-the-fly from some audio file, eg., MP3? It's probably a few lines of code, right? So can't you just hide it behind a LoadMP3(memorystream) method instead of forcing the user to do that first? Somewhere in your vast library of code, I suspect there's a function that can do the encoding transparently. :slight_smile:

As far as the complexity goes, consider taking a look at the Web Audio API. (Take a look at the three articles Andrew posted on your blog about 6 months ago.) It handles most of that complexity already. I'm only using this audio player temporarily, as I'll need more than it can support for my "Version 2" product, so I'll be switching to WAA at some point.

The right technical thing to do for your need is to use http requests to fetch the binary audio data from the URL's you have. Then you keep these as binary buffers to feed these in desired order to the Web Audio API to play these. The http requests you can do in TMS WEB Core with the component with TWebHttpRequests. For the Web Audio API, this is covered in the RTL webaudio.pas. So, it is just a matter of glueing this together.

1 Like