Saliya R
Saliya R

Reputation: 131

How to retrieve a MediaStream from a Blob url?

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?

Upvotes: 13

Views: 21480

Answers (3)

Kaiido
Kaiido

Reputation: 136707

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);
}

Upvotes: 5

jave.web
jave.web

Reputation: 15032

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 :)

Upvotes: 3

iischwuerfl
iischwuerfl

Reputation: 81

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

Upvotes: 2

Related Questions