HTML5 Video source as locally stored blob not working anymore
Asked Answered
S

1

11

As of Chrome 80, something seems to have changed in either the way Blobs or IndexedDB work.

Loading a video file as a blob and assigning it to an HTML5 Video element through createObjectURL, still works:

// load the blob through XMLHttprequest
RequestAsBlob("https://devserver/some-video.mp4",
function(blob)
{
  video.src = URL.createObjectURL(blob); 

  // same above, video.src is now "blob:https://devserver/36e15718-e597-4859-95d3-6bc39daaa999"
}

video.play();

Output: Promise {} And the video plays just fine.

Inspecting the blob, it looks like this:

Blob {size: 6752122, type: "video/mp4"}
size: 6752122
type: "video/mp4"
__proto__: Blob
arrayBuffer: ƒ arrayBuffer()
size: (...)
slice: ƒ slice()
stream: ƒ stream()
text: ƒ text()
type: (...)
constructor: ƒ Blob()
Symbol(Symbol.toStringTag): "Blob"
get size: ƒ size()
get type: ƒ type()
__proto__: Object

I used to store the blob to IndexedDB (through LocalForage) and then retrieve it and play it back, as follows. This, no longer

// blob is a blob fetched from indexedDB
video.src = URL.createObjectURL(blob);  

// video.src is now something like this:
// "blob:https://devserver/ec5e1dfe-0884-40e2-ae8c-c6062734d297"

video.play();

Inspecting the retrieved blob, it looks exactly like the one returned by the XMLHttpRequest

However, it doesn't work:

Output: Uncaught (in promise) DOMException: The element has no supported sources.

I can't figure out what is the change that broke what used to work until now. And it gets weirder:

If I get the stored blob, the one that apparently can't be assigned anymore to the video src directly, and I do this...

var url = URL.createObjectURL(cachedblob);

RequestAsBlob(url,
function(blob)
{
 var url = URL.createObjectURL(blob);

 video.src = url;
 video.play();
}

This works!! I am referencing a blob that was stored in indexedDB, creating a url for it, loading it again through XMLHttpRequest just like if it was actually in some remote location, receiving it as a blob again.... and again creating a URL to it... and it works.

It makes no sense. I hope someone can shed some light on this.

Supersensual answered 5/2, 2020 at 8:48 Comment(1)
A chrome bug I filed for the same issue: bugs.chromium.org/p/chromium/issues/detail?id=1055910 (it ended up being a semi-duplicate of yours)Ulberto
M
9

Could repro, even in Canary build (82), you did great opening this issue.

Now, there are easier workarounds than fetching through XHR, for instance in stable (80) you just need to wrap your retrieved Blob in a new one:

video.src = URL.createObjectURL(new Blob( [ blob ] ) ); 

As a fiddle since StackSnippet™ won't allow access to IndexedDB.

However this workaround only seems to work on stable release (80), on Canary (82) we need to actually read the whole Blob to an ArrayBuffer and build a new Blob from that ArrayBuffer:

const buf = await blob.arrayBuffer();
vid.src = URL.createObjectURL( new Blob( [ buf ] ) );

fiddle.
Since this latter also works for stable, and we don't know when they'll be able to fix the bug, you might want to use the second workaround instead.

1: or let me know if you want me to do it.

Marchpast answered 5/2, 2020 at 12:9 Comment(17)
Thanks Kaiido, opened the issue. Somehow I suspect a regression related to.. security? Just a gut feeling. I also see that it should be possible to assign Blobs to HTMLVideo.srcObject directly, but this doesn't seem to work either. I wonder if there's any way to create a MediaTrack from a blob?Supersensual
Per specs we should be able to use srcObject for Blobs and MediaSources yes, but nobody has implemented it for anything else than MediaStreams currently. Transforming the video in your Blob to a MediaStream would require to load it in a video, not really useful. A MediaSource would be more appropriate, but you'd need to read it as an ArrayBuffer, so the new Blob( [ blob.arrayBuffer() ] ) workaround is still the best one.Marchpast
@Supersensual seems that this only happens for mp4 videos, can you confirm?Marchpast
We also have the same issue, apparently it's broken for all mp4's that don't have faststart (moov at the beginning) - we also had opened an issue -bugs.chromium.org/p/chromium/issues/detail?id=1049096 And yes, your solution works (the arrayBuffer one, we already had new Blob from blob and it still stopped working on v80), thanks for that!Bullpen
Since the issue manifests on mpeg 4 part 14 (mp4) videos that have the moov atom at the end of file, the video player has to jump to the end of the file then back to the beginning. I think this might break the caching system or corrupt the cache file, depending on how they actually cache it. If they stream-cache it, jumping around the file can't be good. I think that by requesting the entire arrayBuffer you force the buffer to fully cache before passing it to the player to jump around it, although i wonder it this will work for on larger files (i think the arrayBuffer is kept in RAM).Fricke
@Marchpast confirmed. Something I just remembered: this seems to be exactly what I previously encountered on iOS with some MP4 files, but not all of them. I suppose those could have been what Eek is refeencing (missing faststart) but I am not so familiar with encoding quirks. Unfortunately I can't recall what iOS / Safari versions had this problem but I am fairly sure I saw it happening as recently as last November. By the way is there any quick fix to add faststart to MP4s without having to re-encode?Supersensual
Ps. - here's the issue I admittedly goofily opened on chromium.org, in case you want to contribute: bugs.chromium.org/p/chromium/issues/detail?id=1049072Supersensual
@Supersensual unfortunately therețs no way to move the moov atom without re-encoding the mp4 file. We ended up implementing the fix @Marchpast suggested, to get the arrayBuffer and create a blob from that.Bullpen
@kaiido it seems that the proposed fix breaks videos on iOS safari. Unfortunately I can't verify this by myself as I don't have i...devices. I wonder if you or other have a way to confirm.Supersensual
@Supersensual unfortunately I don't, no. I guess it does break there because of the limited memory this platform allows for the browser. This workaround works by copying the full data in memory, so indeed it may eat up too much. In your position, I would apply that fix only when your video actually failed to load so it does this costly operations only when really needed: jsfiddle.net/z4k6tf2LMarchpast
@kaiido unlikely, in my case I am working with videos that are a couple megabytes large. Moreover, it seems the same how happens on Android Chrome as well. I am not familiar with mobile development in general so I am not sure how to check, but the behavior is pretty consistent.Supersensual
@Supersensual ah? Here the fiddles works on my AndroPhone.Marchpast
@kaiido sorry, my bad. It seems some videos work, some don't, where previously all worked. This, for example, doesn't play on mobile devices even when just simply embedded in a <video> elelement. number137.com/temp/video.htmlSupersensual
@Supersensual well it seems that for this video even loading the video directly doesn't work, and even on desktop FF. This is probably because of YUV444 chroma, seems mobile Chrome has problems with it, Firefox still doesn't support it for sure. But all in all, it's not related to the Chrome bug nor to this answer's workaround.Marchpast
@kaiido Thanks, yes - unrelated. My oversightSupersensual
You can see the issue pretty clearly with this codepen: codepen.io/forresthopkinsa/pen/rNVLePbUlberto
^ this codepen also demonstrates the workaround workingUlberto

© 2022 - 2024 — McMap. All rights reserved.