How to retrieve a MediaStream from a Blob url?

14,233

Solution 1

Note:

URL.createObjectURL(MediaStream) has been deprecated. Do not use it in code anymore, it will throw in any recent browsers.
The premise of the question is still valid though.



There is no built in way to retrieve the original object a blob URL points to.

With Blobs, we can still fetch this blob URL and we'll get a copy of the original Blob.

const blob = new Blob(['hello']);
const url = URL.createObjectURL(blob);

fetch(url)
  .then(r => r.blob())
  .then(async (copy) => {
    console.log('same Blobs?', copy === blob);
    const blob_arr = new Uint8Array(await new Response(blob).arrayBuffer());
    const copy_arr = new Uint8Array(await new Response(copy).arrayBuffer());
    console.log("same content?", JSON.stringify(blob_arr) === JSON.stringify(copy_arr))
    console.log(JSON.stringify(copy_arr));
  })

With other objects though, this won't work...

const source = new MediaSource();
const url = URL.createObjectURL(source);

fetch(url)
  .then(r => r.blob())
  .then(console.log)
  .catch(console.error);

The only way then is to keep track of your original objects.

To do so, we can come up with simple wrappers around createObjectURL and revokeObjectURL to update a dictionary of objects accessible by URL:

(() => {
  // overrides URL methods to be able to retrieve the original blobs later on
  const old_create = URL.createObjectURL;
  const old_revoke = URL.revokeObjectURL;
  Object.defineProperty(URL, 'createObjectURL', {
    get: () => storeAndCreate
  });
  Object.defineProperty(URL, 'revokeObjectURL', {
    get: () => forgetAndRevoke
  });
  Object.defineProperty(URL, 'getFromObjectURL', {
    get: () => getBlob
  });
  const dict = {};

  function storeAndCreate(blob) {
    var url = old_create(blob); // let it throw if it has to
    dict[url] = blob;
    return url
  }

  function forgetAndRevoke(url) {
    old_revoke(url);
    // some checks just because it's what the question titel asks for, and well to avoid deleting bad things
    try {
      if(new URL(url).protocol === 'blob:')
        delete dict[url];
    }catch(e){} // avoided deleting some bad thing ;)
  }

  function getBlob(url) {
    return dict[url];
  }
})();

// a few example uses

// first a simple Blob
test(new Blob(['foo bar']));

// A more complicated MediaSource
test(new MediaSource());

function test(original) {
  const url = URL.createObjectURL(original);
  const retrieved = URL.getFromObjectURL(url);
  console.log('retrieved: ', retrieved);
  console.log('is same object: ', retrieved === original);
  URL.revokeObjectURL(url);
}

Solution 2

video.src is NOT video.srcObject

And yes they will conflict ;) !

video.src takes source URL

video.srcObject takes source OBJECT (currently as of 2019 only MediaStream is safely supported, maybe in the future you could put the Blob directly here, but not now...)

So it depends on what you really want to do:

A) Display what is currently being recorded

You must have MediaStream object available (which you do) and just put it into video.srcObject

navigator.getUserMedia({ video: true, audio: true }, function (localMediaStream) {
    var video = document.querySelector('video');
    video.src = ''; // just to be sure src does not conflict with us
    video.srcObject = localMediaStream;
}

B) Display existing video / just recorded video

video.srcObject = null; // make sure srcObject is empty and does not overlay our src
video.src = window.URL.createObjectURL(THE_BLOB_OBJECT);

THE_BLOB_OBJECT - you either already have one created through File API, or usually if you have some kind of recorder, let's assume in recorder variable, usually there is getBlob() or something similar available like recorder.getBlob() I strongly recommend you use some existing recorder library for this, but to be complete there is an official MediaRecorder API - https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder

So you see you've just combined 2 things together, you just need to separate them and make sure they don't conflict :)

Solution 3

In case you are using angular2, you can use the DOMSanitizer provided in the platform-browser-package:

import { DomSanitizer } from '@angular/platform-browser';
constructor(
    private sanitizer: DomSanitizer) {
}

and then use your stream like the following:

//your code comes here...
video.src = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(stream));

This should only

Share:
14,233

Related videos on Youtube

Kaiido
Author by

Kaiido

(my about me is currently blank)

Updated on July 08, 2022

Comments

  • Kaiido
    Kaiido almost 2 years

    It was possible to get an URL using window.URL.createObjectURL() from a stream like in below code.

    navigator.getUserMedia({ video: true, audio: true }, function (localMediaStream) {
    
        var video = document.querySelector('video');
        video.src = window.URL.createObjectURL(localMediaStream);
        video.onloadedmetadata = function (e) {
            // Do something with the video here.
        };
    }, 
    function (err) {
        console.log("The following error occured: " + err);
    }
    );
    

    Problem is now I have a blob URL like:

    blob:http%3A//localhost%3A1560/f43bed15-da6c-4ff1-b73c-5640ed94e8ee

    Is there a way to retrieve the MediaStream object from that?

  • Nicolas S.Xu
    Nicolas S.Xu over 6 years
    createObjectURL is about to get deprecated
  • Kaiido
    Kaiido about 5 years
    What you say is true, but it doesn't answer the question... In 2013 (when the question was asked) there was no srcObject, the only way to display a MediaStream was then to make a BlobURL pointing to this MediaStream. This has since then been deprecated, but at the time that question was posted, it was the normal way of doing. The question is then "How to retrieve the MediaStream from such a Blob URL pointing to a MediaStream". Because the fetch(blobURL) we can use for a Blob would not work here.
  • Kaiido
    Kaiido about 5 years
    If you wish, with what we can do today, the question would be "How to retrieve a MediaSource object from blobURL where blobURL was generated by doing URL.createObjectURL(MediaSource_instance)."
  • jave.web
    jave.web about 5 years
    @Kaiido you are correct, I've missed the date, although the question then would be how to get MediaStream object, not MediaSource, which doesn't make much sense unless you want to record the video.
  • Kaiido
    Kaiido about 5 years
    I've used MediaSource as an example because it's an other non-Blob object that we can generate a blobURI from (like it was possible to do with MediaStream before). So it's the contemporary equivalent. And there are good cases to want to retrieve an MSE, for one, to be able to append new chunks to it.
  • jave.web
    jave.web about 5 years
    OK, but if are able to append new chunks, aren't you supposed to have the object reference already? (just wondering here :D )