async enqueueBlockingOperation_(run) {
    this.destroyer_.ensureNotDestroyed();

    /** @type {!Array<!shaka.util.PublicPromise>} */
    const allWaiters = [];
    /** @type {!Array<!shaka.util.ManifestParserUtils.ContentType>} */
    const contentTypes = Array.from(this.sourceBuffers_.keys());

    // Enqueue a 'wait' operation onto each queue.
    // This operation signals its readiness when it starts.
    // When all wait operations are ready, the real operation takes place.
    for (const contentType of contentTypes) {
      const ready = new shaka.util.PublicPromise();
      const operation = {
        start: () => ready.resolve(),
        p: ready,
        uri: null,
      };

      const queue = this.queues_.get(contentType);

      queue.push(operation);
      allWaiters.push(ready);

      if (queue.length == 1) {
        operation.start();
      }
    }

    // Return a Promise to the real operation, which waits to begin until
    // there are no other in-progress operations on any SourceBuffers.
    try {
      await Promise.all(allWaiters);
    } catch (error) {
      // One of the waiters failed, which means we've been destroyed.
      goog.asserts.assert(
          this.destroyer_.destroyed(), 'Should be destroyed by now');
      // We haven't popped from the queue.  Canceled waiters have been removed
      // by destroy.  What's left now should just be resolved waiters.  In
      // uncompiled mode, we will maintain good hygiene and make sure the
      // assert at the end of destroy passes.  In compiled mode, the queues
      // are wiped in destroy.
      if (goog.DEBUG) {
        for (const contentType of contentTypes) {
          const queue = this.queues_.get(contentType);
          if (queue.length) {
            goog.asserts.assert(queue.length == 1,
                'Should be at most one item in queue!');
            goog.asserts.assert(allWaiters.includes(queue[0].p),
                'The item in queue should be one of our waiters!');
            queue.shift();
          }
        }
      }
      throw error;
    }

    if (goog.DEBUG) {
      // If we did it correctly, nothing is updating.
      for (const contentType of contentTypes) {
        goog.asserts.assert(
            this.sourceBuffers_.get(contentType).updating == false,
            'SourceBuffers should not be updating after a blocking op!');
      }
    }

    // Run the real operation, which can be asynchronous.
    try {
      await run();
    } catch (exception) {
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.MEDIA,
          shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
          exception,
          this.video_.error || 'No error in the media element',
          null);
    } finally {
      // Unblock the queues.
      for (const contentType of contentTypes) {
        this.popFromQueue_(contentType);
      }
    }
  }
Enqueue an operation which must block all other operations on all
SourceBuffers.

@param {function():(Promise|undefined)} run
@return {!Promise}
@private