static async request_(uri, request, requestType, init, abortStatus,
      progressUpdated, headersReceived, streamDataCallback, minBytes) {
    const fetch = shaka.net.HttpFetchPlugin.fetch_;
    const ReadableStream = shaka.net.HttpFetchPlugin.ReadableStream_;
    let response;
    let arrayBuffer = new ArrayBuffer(0);
    let loaded = 0;
    let lastLoaded = 0;
    let headers = {};

    // Last time stamp when we got a progress event.
    let lastTime = Date.now();

    try {
      // The promise returned by fetch resolves as soon as the HTTP response
      // headers are available. The download itself isn't done until the promise
      // for retrieving the data (arrayBuffer, blob, etc) has resolved.
      response = await fetch(uri, init);
      // At this point in the process, we have the headers of the response, but
      // not the body yet.
      headers =
          shaka.net.HttpFetchPlugin.headersToGenericObject_(response.headers);
      headersReceived(headers);

      // In new versions of Chromium, HEAD requests now have a response body
      // that is null.
      // So just don't try to download the body at all, if it's a HEAD request,
      // to avoid null reference errors.
      // See: https://crbug.com/1297060
      if (init.method != 'HEAD') {
        goog.asserts.assert(response.body,
            'non-HEAD responses should have a body');

        const contentLengthRaw = response.headers.get('Content-Length');
        const contentLength =
            contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;

        // Fetch returning a ReadableStream response body is not currently
        // supported by all browsers.
        // Browser compatibility:
        // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
        // If it is not supported, returning the whole segment when
        // it's ready (as xhr)
        if (!response.body) {
          arrayBuffer = await response.arrayBuffer();
          const currentTime = Date.now();
          // If the time between last time and this time we got progress event
          // is long enough, or if a whole segment is downloaded, call
          // progressUpdated().
          progressUpdated(currentTime - lastTime, arrayBuffer.byteLength, 0);
        } else {
          // Getting the reader in this way allows us to observe the process of
          // downloading the body, instead of just waiting for an opaque
          // promise to resolve.
          // We first clone the response because calling getReader lock the body
          // stream; if we didn't clone it here, we would be unable to get the
          // response's arrayBuffer later.
          const reader = response.clone().body.getReader();
          const start = (controller) => {
            const push = async () => {
              let readObj;
              try {
                readObj = await reader.read();
              } catch (e) {
                // If we abort the request, we'll get an error here.
                // Just ignore it
                // since real errors will be reported when we read
                // the buffer below.
                shaka.log.v1('error reading from stream', e.message);
                return;
              }
              if (!readObj.done) {
                loaded += readObj.value.byteLength;
                if (streamDataCallback) {
                  await streamDataCallback(readObj.value);
                }
              }

              const currentTime = Date.now();
              const chunkSize = loaded - lastLoaded;
              // If the time between last time and this time we got
              // progress event is long enough, or if a whole segment
              // is downloaded, call progressUpdated().
              if ((currentTime - lastTime > 100 && chunkSize >= minBytes) ||
                  readObj.done) {
                const numBytesRemaining =
                    readObj.done ? 0 : contentLength - loaded;
                progressUpdated(currentTime - lastTime, chunkSize,
                    numBytesRemaining);
                lastLoaded = loaded;
                lastTime = currentTime;
              }

              if (readObj.done) {
                goog.asserts.assert(!readObj.value,
                    'readObj should be unset when "done" is true.');
                controller.close();
              } else {
                controller.enqueue(readObj.value);
                push();
              }
            };
            push();
          };
          // Create a ReadableStream to use the reader. We don't need to use the
          // actual stream for anything, though, as we are using the response's
          // arrayBuffer method to get the body, so we don't store the
          // ReadableStream.
          new ReadableStream({start}); // eslint-disable-line no-new
          arrayBuffer = await response.arrayBuffer();
        }
      }

      if (request.headers['Range']) {
        const range = request.headers['Range'].replace('bytes=', '').split('-')
            .filter((r) => r).map((r) => parseInt(r, 10));
        if (range.length == 2 &&
            arrayBuffer.byteLength != (range[1] - range[0] + 1)) {
          shaka.log.alwaysWarn(
              'Payload length does not match range requested bytes',
              request, response);
        }
      }
    } catch (error) {
      if (abortStatus.canceled) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.RECOVERABLE,
            shaka.util.Error.Category.NETWORK,
            shaka.util.Error.Code.OPERATION_ABORTED,
            uri, requestType);
      } else if (abortStatus.timedOut) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.RECOVERABLE,
            shaka.util.Error.Category.NETWORK,
            shaka.util.Error.Code.TIMEOUT,
            uri, requestType);
      } else {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.RECOVERABLE,
            shaka.util.Error.Category.NETWORK,
            shaka.util.Error.Code.HTTP_ERROR,
            uri, error, requestType);
      }
    }

    return shaka.net.HttpPluginUtils.makeResponse(headers, arrayBuffer,
        response.status, uri, response.url, request, requestType);
  }
@param {string} uri
@param {shaka.extern.Request} request
@param {shaka.net.NetworkingEngine.RequestType} requestType
@param {!RequestInit} init
@param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
@param {shaka.extern.ProgressUpdated} progressUpdated
@param {shaka.extern.HeadersReceived} headersReceived
@param {?function(BufferSource):!Promise} streamDataCallback
@param {number} minBytes
@return {!Promise<!shaka.extern.Response>}
@private