parseAESDrmTag_(drmTag, playlist, getUris, variables) {
    // Check if the Web Crypto API is available.
    if (!window.crypto || !window.crypto.subtle) {
      shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
          'AES. (Web Crypto only exists in secure origins like https)');
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.MANIFEST,
          shaka.util.Error.Code.NO_WEB_CRYPTO_API);
    }

    // HLS RFC 8216 Section 5.2:
    // An EXT-X-KEY tag with a KEYFORMAT of "identity" that does not have an IV
    // attribute indicates that the Media Sequence Number is to be used as the
    // IV when decrypting a Media Segment, by putting its big-endian binary
    // representation into a 16-octet (128-bit) buffer and padding (on the left)
    // with zeros.
    let firstMediaSequenceNumber = 0;
    let iv;
    const ivHex = drmTag.getAttributeValue('IV', '');
    if (!ivHex) {
      // Media Sequence Number will be used as IV.
      firstMediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
          playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
    } else {
      // Exclude 0x at the start of string.
      iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
      if (iv.byteLength != 16) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MANIFEST,
            shaka.util.Error.Code.AES_128_INVALID_IV_LENGTH);
      }
    }

    const keyUris = shaka.hls.Utils.constructSegmentUris(
        getUris(), drmTag.getRequiredAttrValue('URI'), variables);
    const keyMapKey = keyUris.sort().join('');
    const aesKeyInfoKey =
        `${drmTag.toString()}-${firstMediaSequenceNumber}-${keyMapKey}`;
    if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
      // Default AES-128
      const keyInfo = {
        bitsKey: 128,
        blockCipherMode: 'CBC',
        iv,
        firstMediaSequenceNumber,
      };

      const method = drmTag.getRequiredAttrValue('METHOD');
      switch (method) {
        case 'AES-256':
          keyInfo.bitsKey = 256;
          break;
        case 'AES-256-CTR':
          keyInfo.bitsKey = 256;
          keyInfo.blockCipherMode = 'CTR';
          break;
      }

      // Don't download the key object until the segment is parsed, to avoid a
      // startup delay for long manifests with lots of keys.
      keyInfo.fetchKey = async () => {
        if (!this.aesKeyMap_.has(keyMapKey)) {
          const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
          const request = shaka.net.NetworkingEngine.makeRequest(
              keyUris, this.config_.retryParameters);
          const keyResponse = this.makeNetworkRequest_(request, requestType)
              .promise;
          this.aesKeyMap_.set(keyMapKey, keyResponse);
        }
        const keyResponse = await this.aesKeyMap_.get(keyMapKey);

        // keyResponse.status is undefined when URI is "data:text/plain;base64,"
        if (!keyResponse.data ||
            keyResponse.data.byteLength != (keyInfo.bitsKey / 8)) {
          throw new shaka.util.Error(
              shaka.util.Error.Severity.CRITICAL,
              shaka.util.Error.Category.MANIFEST,
              shaka.util.Error.Code.AES_128_INVALID_KEY_LENGTH);
        }

        const algorithm = {
          name: keyInfo.blockCipherMode == 'CTR' ? 'AES-CTR' : 'AES-CBC',
          length: keyInfo.bitsKey,
        };
        keyInfo.cryptoKey = await window.crypto.subtle.importKey(
            'raw', keyResponse.data, algorithm, true, ['decrypt']);
        keyInfo.fetchKey = undefined; // No longer needed.
      };
      this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
    }
    return this.aesKeyInfoMap_.get(aesKeyInfoKey);
  }
@param {!shaka.hls.Tag} drmTag
@param {!shaka.hls.Playlist} playlist
@param {function(): !Array<string>} getUris
@param {?Map<string, string>=} variables
@return {!shaka.extern.aesKey}
@private