Ant Oleyhoo
Ant Oleyhoo

Reputation: 57

createElement for read-only in web worker

I know that accessing the DOM from a web worker is a bad idea for thread safety, but I believe that in my use case it should be perfectly safe.

I'm attempting to take image data from a video at a certain timeframe (for my usecase, over and over again). Because it's a hefty and not crucial process, doing this in a worker thread would be ideal. Here is the (stripped version) code that I would like to run in a worker thread:

 const video = document.createElement('video')
 video.src = 'My video src'
 video.currentTime = desiredTimestamp
 const canvas = document.createElement('canvas')
 const context = canvas.getContext('2d')
 context.drawImage(video, 0, 0, w, h) //paints data onto a canvas
 return canvas.toDataURL() //takes data and makes it a string

When run in a worker, JS throws an error that document is undefined. Is there any way to do this without having to do it in the main thread? If this isn't the correct usecase for workers, is there any other way to do this "in the background"? I want all other processes to have priority over this.

Upvotes: 0

Views: 963

Answers (2)

Kaiido
Kaiido

Reputation: 136638

There is unfortunately still no way to grab a frame from a video in a Worker context.
Maybe the Web-Codecs API will offer that, but it's for the future only anyway.

Currently, the best you can do is to create an ImageBitmap from the video, and pass this to an OffscreenCanvas in a Worker where it will be able to proceed with the still image in an other thread.

Though, since you are only generating these still images a Web-Worker wouldn't help much since the biggest job of grabbing the video frame was already done, and unless you are drawing an extremely big video frame, the whole process shouldn't block your page that much anyway, moreover if you use only the async APIs.

Here is an commented code which I think offers the least prioritized way of grabbing a still from a video.

async function getFrame( url, time ) {

  const video = document.createElement('video');
  video.crossOrigin = 'anonymous';
  // Fetching and seeking are done in parallel, should not block the main thread
  video.src = url;
  video.currentTime = time;
  await new Promise( res => { video.onseeked = res; } );
  
  // createImageBitmap is truly async only with Blobs
  // so if you really want to be sure it doesn't interfere with other running scripts
  // you can wait for the next idle
  await new Promise( res => requestIdleCallback( res ) );
  const img = await createImageBitmap( video );
  // This can be done synchronously, very small computation required
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const context = canvas.getContext('bitmaprenderer')
  context.transferFromImageBitmap( img );

  // You probably don't need a data:// URL so instead we'll generate a blob:// URL
  // Even though the copy of pixels is still done synchronously
  // the compression is most likely done in parallel
  const blob = await new Promise( res => canvas.toBlob( res ) )
  return URL.createObjectURL( blob );
}

getFrame( 'https://upload.wikimedia.org/wikipedia/commons/a/a4/BBH_gravitational_lensing_of_gw150914.webm', 2 ).then( url => {
  const img = new Image();
  document.body.append( img );
  img.src = url;
} )
.catch( console.error );

Upvotes: 1

Ant Oleyhoo
Ant Oleyhoo

Reputation: 57

An answer would be the background tasks API, found here: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API I'd rather do this load on a background thread, so leaving this up but this is what I'll use unless something better comes up.

Upvotes: 0

Related Questions