kuu
kuu

Reputation: 865

Loading audio via a Blob URL fails in Safari

Following code works in Chrome (22.0) but not in Safari (6.0)

<!DOCTYPE html>
<html>
<head>
<script>
function onGo(e) {
  var fr = new FileReader();
  var file = document.getElementById("file").files[0];
  fr.onload = function(e) {
      var data = new Uint8Array(e.target.result);
      var blob = new Blob([data], {type: 'audio/mpeg'});
      var audio = document.createElement('audio'); 
      audio.addEventListener('loadeddata', function(e) { 
          audio.play();
        }, false);
      audio.addEventListener('error', function(e) {
          console.log('error!', e);
        }, false);
      audio.src = webkitURL.createObjectURL(blob);    
    };
  fr.readAsArrayBuffer(file);
}
</script>
</head>
<body>
  <input type="file" id="file" name="file" />
  <input type="submit" id="go" onclick="onGo()" value="Go" />
</body>
</html>

In Safari, neither callback (loadeddata nor error) is called. The content used is an mp3 file, which is normally played back with audio tag. Is there any special care needed for Safari?

Upvotes: 22

Views: 10320

Answers (6)

Marcel
Marcel

Reputation: 15722

As others have stated, the OP's code should work just fine, in general.

However, with iOS/WebKit specifically, this code fails when the URL contains fragments, e.g. like in this question, that asks specifically about fragments. A longstanding issue has been filed.

The solution I used, unfortunately, was to abandon usage of fragments in conjunction with blob URLs altogether.

Upvotes: 0

Adamo Figueroa
Adamo Figueroa

Reputation: 438

This is what works for me, safari and chrome.

  function getBlob(url: string): Promise<Blob> {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.responseType = 'blob';
      xhr.overrideMimeType('audio/mp3');

      xhr.onload = (event) => {
        var blob = xhr.response;
        resolve(blob);
      };
      xhr.onerror = (event) => {
        reject(event);
      };

      xhr.open('GET', url);
      xhr.send();
    });
  }

The function gets the blob, the trick for safari is to override the file type in the request, xhr.overrideMimeType('audio/mp3');

Then call it async way with the URL audio you want to play

const blob = await this.getBlob(url);

const source = URL.createObjectURL(blob);

const audio = new Audio(source);
audio.load();
audio.play();

Upvotes: 0

Vladyslav
Vladyslav

Reputation: 1

Just use source tag in audio.

<audio controls>
  <source src="blob" type="blobType">
</audio>

Upvotes: 0

lastmjs
lastmjs

Reputation: 1014

Many years later, I believe the example in the OP should work just fine. As long as you somehow set the mime type when creating the blob, like the OP does above with the type property of the options passed in:

new Blob([data], {type: 'audio/mpeg'});

You could also use a <source> element inside of an audio element and set the type attribute of the <source> element. I have an example of this here:

https://lastmjs.github.io/safari-object-url-test

And here is the code:

const response = await window.fetch('https://upload.wikimedia.org/wikipedia/commons/transcoded/a/ab/Alexander_Graham_Bell%27s_Voice.ogg/Alexander_Graham_Bell%27s_Voice.ogg.mp3');

const audioArrayBuffer = await response.arrayBuffer();
const audioBlob = new Blob([audioArrayBuffer]);
const audioObjectURL = window.URL.createObjectURL(audioBlob);

const audioElement = document.createElement('audio');

audioElement.setAttribute('controls', true);
document.body.appendChild(audioElement);

const sourceElement = document.createElement('source');

audioElement.appendChild(sourceElement);

sourceElement.src = audioObjectURL;
sourceElement.type = 'audio/mp3';

I prefer just setting the mime type of the blob when creating it. The <source> element src attribute/property cannot be updated dynamically.

Upvotes: 23

Steven
Steven

Reputation: 93

I have the same problem, and I spend a couple days troubleshooting this already. As pwray mentioned in this other post, Safari requires file extensions for media requests:

HTML5 Audio files fail to load in Safari

I tried to save my blob to a file, named it file.mp3 and Safari was able to load the audio that way, but after I renamed the file to have no extension (just "file"), it didn't load. When I tried the url created from the blob in another tab in Safari:

url = webkitURL.createObjectURL(blob);

it download a file right away called "unknown", but when I tried the same thing in Chrome (also on Mac), it showed the content of the file in the browser (mp3 files start with ID3, then a bunch of non-readable characters). I couldn't figure out yet how I could force the url made of blob to have an extension, because usually it looks like this:

blob:https://example.com/a7e38943-559c-43ea-b6dd-6820b70ca1e2

so the end of it looks like a session variable.

This is where I got stuck and I would really like to see a solution from some smart people here. Thanks, Steven

Upvotes: 5

JPorry
JPorry

Reputation: 1

Sometimes, HTML5 audio can just stop loading without any apparent reason. If you take a look to the Media Events (http://www.w3schools.com/tags/ref_eventattributes.asp) you´ll see an event called: "onStalled", the definition is "Script to be run when the browser is unable to fetch the media data for whatever reason" and it seems that it should be helpful for you.

Try listening for that event and reloading the file if necessary, with something like this:

audio.addEventListener('onstalled', function(e) { 
      audio.load();
    }, false);

I hope it helps!

Upvotes: 0

Related Questions