Loading audio via a Blob URL fails in Safari
Asked Answered
E

6

20

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?

Erased answered 5/11, 2012 at 12:14 Comment(1)
See #4202076 which seems to solve the problemCumae
C
19

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.

Corbel answered 27/5, 2019 at 18:8 Comment(2)
I've found that you need to pass the mimetype as the second parameter of Blob() to use the blob as the src attribute of an audio element as described in this WebKit bug here bugs.webkit.org/show_bug.cgi?id=245056. The default mimetype, an empty string, fails. I have no idea why it works with the source element but not with the audio element…Dicker
It still fails for me, when the blob URL has a fragment part.Pumphrey
S
5

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

Seitz answered 8/11, 2017 at 15:41 Comment(5)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewErving
Sure, but I wanted to send it as comment first and I couldn't, that's why I sent it as answer even though is not an answer, but it can provide some additional information to help with troubleshooting.Seitz
So I did a lot of reasearch on this and it seems like Safari just simply cannot play the audio streamed from the blob, because Safari is looking for an extension (.mp3) to identify the source, otherwise it shows "Error" in the audio element. After converting the blob to an URL with this line: audio.src = webkitURL.createObjectURL(blob); your audio.src will look like this: blob:https://example.com/a7e38943-559c-43ea-b6dd-6820b70ca1e2 and it doesn't have an extensionSeitz
I have the same problem, I need to set the source of an audio tag to a blob in that format (blob url format like: blob:https:/ /example.com/a7e38943-559c-43ea-b6dd-6820b70ca1e2). It is working in Chrome, Firefox but not in Safari or Edge. Any suggestionsMineraloid
Its 2023 and I'm still facing the same issue. Passing the type to blob is working only locally. Does anybody have any solution to this?Gilliland
P
0

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!

Principality answered 14/2, 2014 at 11:57 Comment(0)
G
0

Just use source tag in audio.

<audio controls>
  <source src="blob" type="blobType">
</audio>
Goldshlag answered 30/4, 2022 at 8:55 Comment(0)
I
0

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();
Idiocy answered 21/11, 2023 at 5:0 Comment(0)
P
0

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.

Pumphrey answered 1/3, 2024 at 10:26 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.