How to load MP3 files from a FileReference

FileReference.load is a nice new feature in Flash Player 10, but the only thing you get back from it is the FileReference.data property, which is a ByteArray. This is useless (well, not altogether, as we’ll see) in the case of MP3 audio files, because the built-in Sound API does not support loading from ByteArray.

When “making-some-noise” wasn’t quite so easy, I invested a bit of time messing around with MP3 data to see if my Flash sound hack for playing PCM audio data could be subverted to use MP3 data instead. While I got this partially working, I realised that it was a library without any application. What use was it? I tried to use it for shoutcast streams, but it is not possible to seamlessly stitch together arbitrarily small frames of MP3 data and fire them with a “silent sound”. Unfortunately the ramp-up time of the mp3 decoder means that the signal audibly drops out at the stitch points.

It turns out that my efforts were not in vain, because with the arrival of FileReference.load, I have been able to repurpose this library so that it is now possible to load a Sound object with the data provided from a FileReference instance.

When the MP3 data is loaded into a ByteArray, the first thing that needs to be done is to find the first valid MP3 frame header in that ByteArray. Once this is done, it is possible to read the ByteArray data a frame at a time, and to inject these frames into SWF bytecode. The idea is to build an in-memory image of a SWF with an MP3 sound in the library. It is then possible to load the ByteArray using flash.display.Loader.loadBytes . Once this SWF is loaded, we can then get at the Sound class in its library using the following code: loaderInfo.applicationDomain.getDefinition("SoundClass") as Class.

It turns out that there’s quite a few things that can screw you up, mainly because of all the additional headers (id3,id3v2), and also that MP3s frequently appear to be corrupt (especially the ones I encoded in 1998). The MP3 parser should deal gracefully with these issues.

Yesterday (along with previous sittings) was a world of pain with my hex editor. I don’t think there’s any point holding back this part of my audio library, as it seems to work with every mp3 I throw at it now, so here it is:

MP3FileReferenceLoaderLib.zip

I’ve kept the API to a complete minimum, so it’s pretty easy to use. Here’s an example of how to use it (included in the package above):


Download this code: mp3filereferencetest.as

It’s important to remember that you can only call FileReference.browse in response to user interaction (e.g. a mouse click/keyboard event). So it’s a simple as getting a valid FileReference, and handing it to MP3FileReferenceLoader.getSound, and listening to the COMPLETE event that it emits when it has finished.

Annoyingly, all the id3 tags get stripped using this method, and I’m far too lazy to parse these too. So if anyone’s got an ID3/ID3v2 parsing library to hand, get in touch on the email in the source files.

FOOTNOTE: It’s been reported that in Flexbuilder, when you export a release build, FileReference.browse fails. This does not affect the automatic debug build. Compiler option -debug=false and -optimize=true should build a release swf in the debug folder without this problem.

57 Responses to “How to load MP3 files from a FileReference”

  1. {Code}Trip » Astro - FileReference + Sound API Says:

    [...] Existe um problema que é o seguinte: as amostras a ser injectadas no buffer têm de ser em formato PCM mas os dados que temos acesso ao ler o ficheiro correspondem ao ficheiro mp3 que está codificado. Por isso é necessário usar um workaround que sustenta na capacidade de do Flash Player criar um SWF em bytecode na memoria. O método não paece muito simples (1,2), mas felizmente hoje o Christopher Martin-Sperry (FlexibleFactory) disponibilizou um biblioteca que faz o serviço por nós com a nuance de perder a informação relativa ID3 tag no processo. No final obtem-se um objecto Sound com o qual já podemos utilizar o método extract() para ter acesso a amostra codificadas em PCM. [...]

  2. A question to FlexibleFactory about FileReference.load Says:

    [...] I enjoyed your latest post, How to load MP3 files from a FileReference. However this did not work in the Internet (e.g., http://boreal-kiss.com/xxx.swf), while it does work locally (not a local site but a simple browsing test). When the swf file run in the network, the FileReference.load (or some similar method) gives an “IllegalOperationError #2014: Feature is not available at this time”. So the question is “did you verify whether your program work even in the Internet?”. [...]

  3. FileReference.loadでサウンドをロードするライブラリ Says:

    [...] FileReference.loadは読み込ん データがByteArray型になるため、ByteArrayを読み込むメソッドがないSoundクラスの 合かなり不便。FlexibleFactoryでFileReference.loadで選択したMP3ファイルを再生するパッケージが配布されている。使い方も超簡単。ローカルのMP3からサウンドスペクトラ を表示するサンプルを作ってみた。自分のチョイスした曲を無理矢理聴かせるよりは良心的かな。 [...]

  4. mellomutt Says:

    Getting this error: “call to undefined method load”. A FileReference doesn’t have a load method. I’m very new to AS3 coding, so any suggestions are appreciated. Here’s the code from your possibly awesome work that’s throwing up errors:

    internal function loadFileRef(fileRef:FileReference):void
    {
    fileRef.addEventListener(Event.COMPLETE,loaderCompleteHandler);
    fileRef.addEventListener(IOErrorEvent.IO_ERROR,errorHandler);
    //fileRef.addEventListener(Event.COMPLETE,loaderCompleteHandler);
    fileRef.load();// <- no load method exists for fileRef
    }

  5. chris Says:

    @mellomutt: this is for flash player 10 beta only, and currently the code will only work for beta 1. Unfortunately I cannot develop with beta 2 until they sort out the bugs that prevent it running on my pc. It’s also worth noting that the callback events from the Sound object are named differently in beta 2.

  6. Matt Rix Says:

    This is a neat little library, and it works fine in the newest Flash 10 RC that I’ve got. The biggest problem(and perhaps why it appeared to not work) is that the fileRef.load() method can only load files up to 512kb, which most mp3s are well over.

  7. chris Says:

    I asked for an official line on this issue here

  8. chris Says:

    Seems to be fixed in “the most recent version”

  9. cip Says:

    Nice work!

    I got a hold of the production release of Flash CS4 at Adobe FlashCamp and this is working perfectly.

  10. Dmitri Says:

    Hey, here is an ID3 parsing library developed by a cool duded called Ben Stucki

    http://blog.benstucki.net/?id=25

  11. Beckers Francois-Xavier Says:

    hello,
    thanks for this great work! i’ve used it without any problems so far.

  12. First steps with flash 10 audio programming | kelvinluck.com Says:

    [...] to some great work from an old friend of mine, it is possible to dynamically create a Sound object based on an MP3 [...]

  13. Zeh Says:

    Thanks for this. I’m currently using this for my college thesis (ffnnkk.org) and it’s going great. I was dumbfounded we weren’t able to load the data directly into a Sound object… it’s as if we have two great features but they just can’t talk to each other, it’s amazing.

    Anyway. You might be interested in knowing that your approach doesn’t currently work on AIR 1.5 applications. The problem’s that it apparently thinks you’re trying to load an executable. This is the error log:

    SecurityError: Error #3015: Loader.loadBytes() is not permitted to load content with executable code.
    at flash.display::Loader/_loadBytes()
    at flash.display::Loader/loadBytes()
    at org.audiofx.mp3::MP3FileReferenceLoader/generateSound()[.\source\org\audiofx\mp3\MP3FileReferenceLoader.as:135]
    at org.audiofx.mp3::MP3FileReferenceLoader/parserCompleteHandler()[.\source\org\audiofx\mp3\MP3FileReferenceLoader.as:73]
    at flash.events::EventDispatcher/dispatchEventFunction()
    at flash.events::EventDispatcher/dispatchEvent()
    at MP3Parser/loaderCompleteHandler()[.\source\org\audiofx\mp3\MP3Parser.as:78]

    I’m not sure if there’s anything you can do about it, but there it is.

  14. Zeh Says:

    Ouch, spoke too soon: yes there’s a way to solve it.

    Information here:
    http://www.dreaminginflash.com/2008/05/14/%E2%80%9Csecurityerror-error-3015-loaderloadbytes-is-not-permitted-to-load-content-with-executable-code/

    Quick fix:
    org.audiofx.mp3.MP3FileReferenceLoader

    Replace line 135:
    MP3FileReferenceLoader.as

    With this:
    var loaderContext:LoaderContext = new LoaderContext();
    loaderContext.allowLoadBytesCodeExecution = true;
    swfBytesLoader.loadBytes(swfBytes, loaderContext);

    However, this will ONLY work on AIR compilations, so something like this is probably better for full AIR and Plugin support:

    var loaderContext:LoaderContext = new LoaderContext();
    if (Capabilities.playerType == “Desktop”) loaderContext["allowLoadBytesCodeExecution"] = true;
    swfBytesLoader.loadBytes(swfBytes, loaderContext);

    I’ve just tested it and it’s working really well on both versions.

  15. Zeh Says:

    Sorry, bad copy&paste, line 135 (that should be replaced) is this:
    swfBytesLoader.loadBytes(swfBytes);

  16. chris Says:

    Zeh- I was just going to get back to you with the allowLoadBytesCodeExecution tip, but went off on a tangent worrying about security concerns. I’ve never used AIR, so this was untested. I’ll see if I can roll it into the library at some point. I don’t think it will expose any vulns to malformed MP3 (as described here) because the data from the MP3 gets completely mashed up in the creation of the in-memory swf, so trying to use it to inject pernicious code would fail. Cheers.

  17. Zeh Fernando » Blog Archive » Why I love the ActionScript development community Says:

    [...] back to the point. I want to play sound from loaded sound data. A quick search on Google turns out this page, in which a guy talks about the very same issue and makes AS3 libraries for MP3 parsing freely [...]

  18. Zeh Says:

    Thanks Chris. Now that’s in the comments anyone can implement it if the need arises, so no need to worry too much about patching it.

    Let me know in the future if you need any version tested in AIR 1.5 though, I have my project compiling to both Plugin and Air versions in sync (so for me it was important having the class working seamlessly).

  19. Second steps with Flash 10 audio programming | kelvinluck.com Says:

    [...] that there is still a delay when you load an MP3 in my example. This is because I am using the same FileReference.browse > Sound object hack as last time and this needs to loop over the entire loaded mp3 file while turning it into a [...]

  20. Mike Almond Says:

    Very nice work.
    I’ve combined your loader with Ben Stucki’s ID3Reader ( http://lab.benstucki.net/archives/as3/srcview/ ) while making a player 10 visualiser demo.

    See it here:
    http://blog.madebypi.co.uk/2009/03/30/flashplayer-10-audio-fun/

  21. Eldad Says:

    I found a small bug, when passing the same FileRefence to getSound() more than once, the library will play the sound twice on the second call (three times on the third, and so on…)

    I found that changing line 65 and 66 in MP3Parses to:
    if (!fileRef.hasEventListener(Event.COMPLETE)) fileRef.addEventListener(Event.COMPLETE,loaderCompleteHandler);
    if (!fileRef.hasEventListener(IOErrorEvent.IO_ERROR)) fileRef.addEventListener(IOErrorEvent.IO_ERROR,errorHandler);

    prevents it, by preventing the event listener from being attached twice to the same FileReference object

  22. Bo Says:

    How did you ever figure out the swf bytecode for the Sound object?

    It seems that knowing these could be extremely helpful in creating other dynamic embedded classes o- the-fly.

  23. chris Says:

    Hi Bo,

    A while ago I wrote the following article which was really most of the preliminary work behind this article:

    http://www.flashbrighton.org/wordpress/?p=9

  24. » 播放mp3的另一种方法 :: Fluid idea for RIA Says:

    [...] 不过,as3至今不支持动态载入声音数据,比如mp3数据,但牛人总有牛方法,这不:http://www.flexiblefactory.co.uk/flexible/?p=46,这位实现了以二进制方式播放mp3文件。 [...]

  25. Kevin Says:

    hi, I have a question.
    How to load FLV Files and play from a FileReference?
    thx!

  26. Joe Best Says:

    Hi there,

    Fantastic work! I’ve been looking at how to load mp3s locally for some time, and you appear to be my saviour! If I use it I’ll be sure to credit you.

    Thanks!
    J

  27. Joe Says:

    I have a question, if I use an mp3 with a sample rate that isnt 44100, once converted to a sound object, is the sound then converted to 44100?

    Cheers
    J

  28. chris Says:

    Flash resamples everything to the output rate.

  29. links for 2009-06-26 at adam hoyle presents suckmypixel Says:

    [...] FlexibleFactory » Blog Archive » How to load MP3 files from a FileReference (tags: flash sound mp3 as3 actionscript) [...]

  30. Gilbert Says:

    Do you know to make the FileReference.load() to work with FVL or MP4 files?

  31. chris Says:

    @Gilbert: In short, no, although I think it might be possible by analysing the bytecode for FLV/Video embedded into SWF when it is placed on the Flash timeline. However, the docs state that one can expect A/V sync issues when embedding lengthy video using this approach.

    I simply haven’t the time to investigate.

  32. AS3: Loading sound from bytearray « Cambiatablog Says:

    [...] to article by Chris at flexiblefactory.co.uk about reading sound from a FileReference, I could make it work! Here is how his elegant solution works – well, at least “kind [...]

  33. mitesh dave Says:

    hii,
    Some files do not get played can you please help me.
    I convert WAV to MP3 using a dotnet application and when i load those mp3 (which are converted one’s) i get error end of file occured…please help me

  34. Paul Says:

    If you created two instances of the MP3FileReferenceLoader using different mp3 files, are the resulting SoundClass classes from each instance going to step all over eachother’s bytes rendering one or both of them unplayable or corrupt?

    If not, then do dynamic class definitions like this get garbage collected (I’m a flash/flex newbie) ? In other words if I were to play an endless seamless ’stream’ of mp3 files would the flash player or AIR die from eating up memory with those dynamic SoundClass classes?

  35. Paul Says:

    Okay I’ve put an application together and answered my own questions, in case anyone is interested.

    I’ve created an app with two MP3FileReferenceLoaders and two play buttons, and I’ve found that New SoundClass classes do not step on the previous one, they all remain intact.

    I’ve also modified the MP3FileReferenceLoader to accept byteArrays (as opposed to fileReferences) and created 1000 Sound objects every time I select an mp3 file. I’ve found that these runtime SoundClass(es) do get garbage collected.

    All in all this is awesome, it meets my needs. I would recommend abstracting it a bit: maybe separating out any FileReferences from the MP3Parser and Swf generation code.

  36. chris jar Says:

    Hi Paul,

    You wrote 2 days ago: I modified the MP3FileReferenceLoader to accept byteArrays.

    Can you post some code with your solution? I wrote the same code but mine looses the begining and the end of the sound. :(
    Perhaps your solution is better.

    Chris (chrisiek AT gazeta dot pl)

  37. Dale Says:

    Hey Paul / Chris,

    I would love to know how you guys modified the loader to accept bytearrays! A project I’m working on really needs that ability but I just can’t figure out how to go about that! I modified the program so it gets around that problem … but that would open a whole lot of doors over here!

    Any help would be very appreciated!

    Thanks,
    Dale

    ddonahue@electricfunstuff.com

  38. bechar Says:

    Hi, how to same thing to play mp3 file without opening FileReference, actuly i want to open mp3 directly from the server, any luck to open directly file via server and i dont want to show path to end user..or any encode/decode of mp3 using AS3.0 algo or tutorial, if any help please..please…

    Thanks
    Bechar

  39. paul Says:

    that’s genius.
    thanks for sharing!

  40. MoodyLight Sound Visualizer | youtag graphic design Says:

    [...] of local MP3 files is taken care of by the genius MP3FileReferenceLoader class by Christopher Martin-Sperry [requires Flash Player 10 or [...]

  41. jerry Says:

    Would like to see the code for accepting ByteArrays instead of FileReference objects as well.

  42. jerry Says:

    Dale, I put together the follow hacks to get it working with ByteArray. I needed to load directly from a ByteArray as well.
    In MP3FileReferenceLoader.as replace the constructor with:

    /**
    * Constructs an new MP3FileReferenceLoader instance
    *
    */
    public function MP3FileReferenceLoader()
    {
    mp3Parser = new MP3Parser(this);
    mp3Parser.addEventListener(Event.COMPLETE,parserCompleteHandler);

    }

    add:

    public function parsingDone(parser:MP3Parser):void
    {
    generateSound(parser);
    }

    in MP3Parser.as add member variable:

    private var m_parent:MP3FileReferenceLoader;

    change constructor to:

    public function MP3Parser(parent:MP3FileReferenceLoader)
    {
    loader=new URLLoader();
    loader.dataFormat=URLLoaderDataFormat.BINARY;
    loader.addEventListener(Event.COMPLETE,loaderCompleteHandler);
    m_parent = parent;
    }

    and add

    public function loadMP3ByteArray(bytes:ByteArray):void
    {
    mp3Data = bytes;
    currentPosition = getFirstHeaderPosition();
    m_parent.parsingDone(this);
    }

    This isn’t elegant, but it works. Let me know how it goes.

  43. Radu Says:

    Hi,
    First off, this is a huge help. Chris you’re the absolute man !
    Jerry, thank you too, I’ve changed the code using your help but I get this:

    Found id3v2 header, length 890 bytes. Moving to 89a
    Error: Could not locate first header. This isn’t an MP3 file…

    I’m 99% sure it’s an MP3 there.

  44. chris Says:

    @radu: I’m fairly sure there’s a bug in the initial frame seeking code around the area you mention. I think the scan should advance a byte at a time, which it does not at the moment. The ID3 skip code is good, so the seek should start again at that offset, again advancing a byte at a time.

    The line in MP3Parser.as:

    var val:uint=mp3Data.readInt();

    advances mp3Data.position by 4 bytes. My belief is that if one was to store the position immediately prior to this line, do the read, then reset the mp3Data.position to the stored position+1 soon after this line, then the initial frame seek will be far more robust, it’s a definite bugfix and it might even solve your issue!

    To implement this you would need to replace this code:

                                    var val:uint=mp3Data.readInt();
                                    if(isValidHeader(val))
                                    {
                                            parseHeader(val);
                                            mp3Data.position=readPosition+getFrameSize(val);
                                            if(isValidHeader(mp3Data.readInt()))
                                            {
                                                    return readPosition;
                                            }
                                    }
    

    With:

                                    var storedPos:uint=mp3Data.position; //store it
                                    var val:uint=mp3Data.readInt();
                                    if(isValidHeader(val))
                                    {
                                            parseHeader(val);
                                            mp3Data.position=readPosition+getFrameSize(val);
                                            if(isValidHeader(mp3Data.readInt()))
                                            {
                                                    return readPosition;
                                            }
                                    }
                                    mp3Data.position=storedPos+1; //restore it
    

    I haven’t compiled this code, so it is completely untested.

    Sorry I haven’t got time to mod the release myself. Let me know if this helps.

  45. Radu Says:

    No it didn’t. Thanks for your reply anyway. I think it’s because of how I write the byteArray.
    Thanks for your hard work Chris. This library is amazing !

  46. Neil Says:

    This is great work, many thanks for sharing with the community. Might I suggest you set up a googlecode project as I can see this getting extended in the future.

  47. chris Says:

    @Neil, or anyone else. Unless I’m mistaken, anyone can start a project with this code. I hope I haven’t disallowed this inadvertently with MIT license. I’m too busy to bother with all that at the moment. Would be happy to contribute bits and bobs and link to the project.

  48. Ady Levy Says:

    hi mate,
    great job you’ve done there !
    i’d like to use your script in my multiple file uploader script, what do you say ?

  49. chris Says:

    @Ady: As long as you conform to the license, then that’s fine. MIT is pretty easy going on that front.

  50. Gerry Beauregard Says:

    Thanks for this excellent library Chris! The ability to load and decode a local mp3 file, in combination with the ability to send arbitrary audio data to the output using SampleDataEvent, opens up exciting possibilities for pretty sophisticated audio applications that run right in the browser. Here’s a real-time audio pitch-shifting and time-stretching application I wrote: http://www.audiostretch.com.

  51. Moorscode Says:

    When using code to load different files after each other it’s recommended to empty the ByteArray (if you define it globally). Otherwise the Sound.extract will ADD the data to the array.

    samples = new ByteArray();
    var lng:Number = mySound.extract(samples, extract);

    I was wondering if you have got the WAVE data playing code hanging around somewhere, I would be interrested to see that. It would surely save me some time..

    Great example and nice of you to share with the world Chris!

    Kind regards,
    J Moors(code)

  52. Moorscode Says:

    The issue some people have with loading bigger files, which happend to me too, is due to the ‘file reference needs to stay in scope while loading’ comment in the flash documentation.

    When you modify the MP3Parser and add a

    private var _fileRef:FileReference;

    And modify the loadFileRef function to set the localized variable, the FileReference stays in scope and you can load bigger files again.

    internal function loadFileRef (fileRef:FileReference):void {
    _fileRef = fileRef;
    // add listeners to the _fileRef instead of fileRed
    }

    J Moors(code)

  53. miles Says:

    it doesn’t work at adobe flash player 10.1 prerelease : (
    may be somebody know why?

  54. chris Says:

    Miles, we’ve had a lot of issues with pre-release versions of Flash, and I’ve burned many hours trying to solve issues that just disappear in subsequent alphas. As I am extremely busy on other projects, I am not willing to give up any time into investigating this. If it is important to you, please make sure the player team are aware of this difference. I hope that this library is sufficiently prevalent for this to be important to them (although getting to the bottom of it might be more complicated than a lot of the issues that get reported, due to the hand-crafted and potentially buggy bytecode in it ;) )

  55. miles Says:

    Thanks a lot!
    It seems new pre-release version a one big bug : )

  56. Anthony Says:

    Awesome code, many thanks to Chris and the contributors.

    On the topic of loading a ByteArray, using Jerry’s code w/:

    public function getSoundBA(ba:ByteArray):void
    {
    mp3Parser.loadMP3ByteArray(ba);
    }

    in MP3FileReferenceLoader did the trick. (so one calls getSoundBA instead of getSound)

  57. MP3 Wave Display Says:

    [...] Comps. To load an MP3 file and use it in Flash as a Sound object I used FlexibleFactory’s MP3FileReferenceLoader library. For debugging I used the Yalog logging tool by Stephan Bezoen. Very nice Air application. [...]

Leave a Reply