14

I am trying to create a function in which an mp3 file is pulled from

<input type="file" id="sound" accept="audio/*"/>

and stored in localStorage within Chrome. (I know localStorage is limited in size, but the audio files that I will be working with will be under 2mb.) Once this audio file is stored, it will be played with:

var audio = new Audio('audioFile');
audio.play();

I am still new to web development, so any pointers would be great!

NOTE: This is not a duplicate of HTML5 record audio to file. I am trying to take a preexisting mp3 file from my HDD and storing it into localStorage so that it can be played back.

Community
  • 1
  • 1
RockGuitarist1
  • 638
  • 6
  • 15
  • Possible duplicate of [HTML5 record audio to file](http://stackoverflow.com/questions/16413063/html5-record-audio-to-file) – Saeed Ludeen May 06 '17 at 03:05
  • What have you tried so far? Please show your work. – Soviut May 06 '17 at 03:05
  • Currently I am only providing an assortment of mp3 files in which a user can choose from. What I have tried so far was to use getElementById to get the path from "file" and then use that inside of new Audio, but that isn't correct. I haven't even touched trying to save to localStorage yet. – RockGuitarist1 May 06 '17 at 03:09
  • In the future, at least make an attempt before coming to SO. This isn't an advice forum, it's for concrete Q&A. – Soviut May 06 '17 at 03:14
  • 3
    I've scoured for a few hours before resorting to here. I prefer to try to figure out a process before coming here to get yelled at. – RockGuitarist1 May 06 '17 at 03:15

2 Answers2

14

localStorage is not meant to store such data. Instead, you should use the IndexedDB API, which is made for storing this kind of data.

Dealing with the indexedDB is highly simplified with the Mozilla's localForage lib, which provides a syntax similar to the one of localStorage.

Fiddle since StackSnippets® doesn't allow access to indexedDB nor localStorage.


But to convert a File (or a Blob) to a String, as needed by the localStorage API, you can use an FileReader and its readAsDataURL() method, which will provide an dataURI, directly playable by MediaElements.

f.onchange = e =>{
  if(f.files[0].type.indexOf('audio/') !== 0){
    console.warn('not an audio file');
    return;
    }
  const reader = new FileReader();
  reader.onload = function(){
    var str = this.result;
    // this is a string, so you can store it in localStorage, even if it's really not a good idea
    console.log(str);
    const aud = new Audio(str);
    aud.play();
    };
  reader.readAsDataURL(f.files[0]);
  }
<input type="file" id="f">
Community
  • 1
  • 1
Kaiido
  • 87,051
  • 7
  • 143
  • 194
  • I wish I could use this, but sadly whenever I add localforage into the script source, I get this error: "Refused to load the script because it violates the following Content Security Policy directive: "script-src 'self' https://syndication.twitter.com"." – RockGuitarist1 May 06 '17 at 14:13
  • @RockGuitarist1, are you trying to load the script from rawgit too ? Your CSP seems to be configured to allow only scripts form the same origin as your page and from twitter. So if you download the script on your server, and load it from your server, it should work. – Kaiido May 06 '17 at 14:19
  • I actually downloaded localforage and imported it into the project. I then used: ` ` and it still threw the error. I am actually working on a open sourced Chrome extension. – RockGuitarist1 May 06 '17 at 14:25
  • Then I guess you'd have to include it in your extension manifest, but my knowledge on extensions is very limited. Note that there is also probably an even better API for saving files from extensions, you've got access to far more powerful APIs than us poor web-devs. But this would change completely your question. – Kaiido May 06 '17 at 14:30
  • Sounds like that is something I would have to do. I have gotten the previous method to work the way I wanted it to, but this method would've been better since I can manipulate audio volume levels. – RockGuitarist1 May 06 '17 at 14:31
  • Don't use the first answer's solution at all. In the worth case, you can use my second snippet, but it's really not a good idea. And once again, since you're working on a chrome extension, look for chrome:// APIs e.g the [storage API](https://developer.chrome.com/apps/storage) may work for you. – Kaiido May 06 '17 at 14:34
7

This is not what localStorage is meant for. localStorage can only store strings and can only accommodate a small amount of date.

If you're really determined to do this you need to:

Note that Base64 encoding usually increases the size of the source material by about 33%. This is because it can take any kind of data, including binary, and converts it to a string made of a 64 character set that's compatible with standard ASCII strings.

To play it back, you need to the opposite

  • retrieve the Base64 encoded string from localStorage
  • decode the Base64 string into an ArrayBuffer
  • load the ArrayBuffer into an Audio object
  • assign the Audio object to an <audio> element

It should also be noted that localStorage is often only given 5-10MB of storage space, depending on the browser. This can be tested here, but generally, keep your mp3 files small by encoding them with lower bit rates, use mono instead of stereo channels where possible, and reduce sample rate if the audio is just spoken words and not music.

Soviut
  • 79,529
  • 41
  • 166
  • 227
  • This is what I am looking for. Thank you! – RockGuitarist1 May 06 '17 at 03:13
  • 2
    Wow don't go through an ArrayBuffer and don't do the b64 conversion yourself... You can simply use an FileReader and its `readAsDataURL` method. And you don't need to pass the dataURI to an arrayBuffer either to play it, you can simply pass the b64 dataURI into the Audio element. And you'd have to explain what is an *Audio object* if it's not an `AudioElement`. – Kaiido May 06 '17 at 06:10
  • 2
    @Kaiido write another answer with a complete explanation if you have a better way of doing this or add what you've said to mine as an edit since you have enough rep. – Soviut May 06 '17 at 06:27