Developing on Staxmanade

How to Base64 and save a binary audio file to local storage and play it back in the browser

(Comments)

I wanted to see if it was possible to save a small audio file into localStorage, read it back out and play the file. In this post I'll show you short example on how to download an audio file, save it to localStorage, read it back and set it up for playback.

Disclaimers

works on my machine

  • This was tested in IE 10 (Win 8), Chrome 46 (Mac), and Firefox 41 (Mac); however, some of the api's and techniques used in this demo are not supported in all browsers, such as the FileReader, Blob, Promise, and fetch api's. The Promise and fetch api's can be polyfilled. There may be polyfills for the other api's, but I haven't researched those.
  • This post isn't going to go into much of the "should I do this", as I'm sure you can come up with many reasons why you shouldn't. But I couldn't find any examples that demonstrated these steps in one place. So I prototyped the idea and am putting it here in case I do want to use this in the future sometime (or maybe you do too).
  • My tests in Chrome didn't go great if I tried to re-run the experiment multiple times. Sometimes it would work, other times it seemed to get into a bad state and always raised a MediaError event. Refreshing the page would get it working again.

First we need an audio file

I don't want to point to any specific audio example since I'd feel bad if some poor soul's hosted mp3 file gets hammered (not likely) because of this example. But you just need a link to a simple, short mp3 (or whatever audio type you're trying to test). If you look at the sample below replace <<SampleAudioUrlHere>> with the link to your test audio file.

Won't fit in localStorage?

If you're trying to save an audio file that's too large as a Base64 encoded audio file will be larger than it's original size and we don't get very much space in localStorage then, ya you're using a file that's too large... Get something smaller or don't to this. Just sayin :P

How does it work?

  1. Use fetch api we can easily get at the blob()
  2. Run the Blob through the FileReader
  3. Which also handily turned it into a data url for us
  4. The data url is just a base64 encoded string which is easy to save to localStorage
  5. Read the string back out of localStorage
  6. Set the audio's src attribute to the audio data url
  7. Profit!

While I was prototyping this I was borrowing someone else short mp3 file and to work around CORS (cross origin http request) I used the handy https://crossorigin.me/<<SampleAudioUrlHere>> service. This may be ok to do for a prototype, but you should't typically run your requests through this service. It's insecure and against pretty much all the different web religions.

Show me the code

This was just a quick get-er-done example. Lots of not-great-practices, but it demonstrates the possibility. Enjoy!

<!DOCTYPE html>
<html>

  <head>
    <script>

      // Code goes here
      var audioFileUrl = '<<SampleAudioUrlHere>>';

      window.onload = function() {

        var downloadButton = document.getElementById('download');
        var audioControl = document.getElementById('audio');

        audioControl.onerror = function(){
          console.log(audioControl.error);
        };

        downloadButton.addEventListener('click', function() {

          audioControl.src = null;

          fetch(audioFileUrl)
            .then(function(res) {
              res.blob().then(function(blob) {
                var size = blob.size;
                var type = blob.type;

                var reader = new FileReader();
                reader.addEventListener("loadend", function() {

                  // console.log('reader.result:', reader.result);

                  // 1: play the base64 encoded data directly works
                  // audioControl.src = reader.result;

                  // 2: Serialize the data to localStorage and read it back then play...
                  var base64FileData = reader.result.toString();

                  var mediaFile = {
                    fileUrl: audioFileUrl,
                    size: blob.size,
                    type: blob.type,
                    src: base64FileData
                  };

                  // save the file info to localStorage
                  localStorage.setItem('myTest', JSON.stringify(mediaFile));

                  // read out the file info from localStorage again
                  var reReadItem = JSON.parse(localStorage.getItem('myTest'));

                  audioControl.src = reReadItem.src;

                });

                reader.readAsDataURL(blob);

              });
            });

        });

      };

    </script>
  </head>

  <body>
    <button id="download">Run Example</button>
    <br />
    <audio controls="true" id="audio" src=""></audio>
  </body>

</html>

I hope you found this quick tutorial useful. Would love to hear any feedback or thoughts on the approach.

As earways, Happy Listening!

Comments