{"record":{"id":"98d24c5a8be7a2e8","repo":"agalwood/Motrix","slug":"unsupported-encryption-98d24c","errorCode":"unsupported-encryption","errorMessage":"AES-128 key missing URI","messagePattern":"AES-128 key missing URI","errorType":"error_code","errorClass":"MediaParseError","httpStatus":null,"severity":"error","filePath":"src/core/media/hls-parser.ts","lineNumber":173,"sourceCode":"  let pendingByteRange: { length: number; offset?: number } | undefined\n\n  const segments: MediaPart[] = []\n  let index = 0\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i] ?? ''\n\n    // EXT-X-KEY\n    if (line.startsWith('#EXT-X-KEY:')) {\n      const attrs = line.slice('#EXT-X-KEY:'.length)\n      const method = attr(attrs, 'METHOD')\n\n      if (method === 'NONE') {\n        activeKey = null\n      } else if (method === 'AES-128') {\n        const keyUri = attr(attrs, 'URI')\n        if (!keyUri)\n          throw new MediaParseError(\n            'unsupported-encryption',\n            'AES-128 key missing URI'\n          )\n        const ivAttr = attr(attrs, 'IV')\n        // IV resolution happens per-segment (for seq-based IV)\n        // Store a sentinel: if ivAttr is present, store the parsed bytes; else null\n        const ivBytes: Uint8Array | null = ivAttr ? parseIvHex(ivAttr) : null\n        // We store the IV on activeKey as a placeholder; per-segment we may override below\n        // Use a special marker: store ivBytes or the placeholder for seq-based\n        activeKey = {\n          method: 'AES-128',\n          uri: resolveUri(url, keyUri),\n          // Temporary: will be resolved per-segment; use seqNumberIv(seq) as default\n          iv: ivBytes ?? seqNumberIv(seq),\n          _explicit: ivAttr !== undefined,\n          _ivBytes: ivBytes,\n        } as KeyRef & { _explicit: boolean; _ivBytes: Uint8Array | null }\n      } else {","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/media/hls-parser.ts#L155-L191","documentation":"MediaParseError with code 'unsupported-encryption', thrown by parseHlsMedia when a #EXT-X-KEY line has METHOD=AES-128 but no URI attribute. AES-128 keys must be fetched from a URI; an AES-128 declaration without one is malformed and there is no key material to decrypt segments.","triggerScenarios":"An HLS media playlist containing #EXT-X-KEY:METHOD=AES-128 (no URI=...). Caused by a broken encoder/packager, an in-place edit that stripped the URI, or a manifest proxy that rewrote the line incompletely.","commonSituations":"Custom manifest filter/rewriter that drops attributes; vendor packager bug; test fixtures hand-written without the URI; copy-paste of an EXT-X-KEY template that left METHOD but removed URI.","solutions":["Inspect the raw playlist and locate the offending #EXT-X-KEY line.","If the URI was stripped by a proxy/rewriter, fix the rewrite rule to preserve URI=.","If the manifest is genuinely malformed, fetch an unmodified copy from the origin.","Pre-validate before parsing: every EXT-X-KEY with METHOD=AES-128 must have a URI attribute."],"exampleFix":"// before — playlist has '#EXT-X-KEY:METHOD=AES-128'\nconst plan = parseHlsMedia(text, url)\n// after — fix the source manifest line to\n// '#EXT-X-KEY:METHOD=AES-128,URI=\"https://cdn.example/key.bin\"'\nconst plan = parseHlsMedia(text, url)","handlingStrategy":"validation","validationCode":"function aes128KeysHaveUri(text: string): boolean {\n  return [...text.matchAll(/#EXT-X-KEY:[^\\n]+/gi)].every(line => {\n    const method = /METHOD=([A-Z0-9-]+)/i.exec(line[0])?.[1]\n    if (method !== 'AES-128') return true\n    return /URI=/i.test(line[0])\n  })\n}\nif (!aes128KeysHaveUri(text)) {\n  throw new Error('AES-128 EXT-X-KEY is missing URI.')\n}","typeGuard":"function aes128KeysHaveUri(text: string): boolean {\n  return [...text.matchAll(/#EXT-X-KEY:[^\\n]+/gi)].every(line => {\n    const method = /METHOD=([A-Z0-9-]+)/i.exec(line[0])?.[1]\n    return method !== 'AES-128' || /URI=/i.test(line[0])\n  })\n}","tryCatchPattern":"try {\n  const plan = parseHlsMedia(text, url)\n} catch (e) {\n  if (e instanceof MediaParseError && e.code === 'unsupported-encryption' && /missing URI/.test(e.message)) {\n    // fetch unmodified manifest from origin\n  } else throw e\n}","preventionTips":["Validate EXT-X-KEY lines before parsing when ingesting third-party manifests.","Avoid manifest-rewriting proxies that drop attributes.","Fetch the raw manifest from origin when a key error appears, to rule out proxy mangling."],"tags":["hls","media","encryption","streaming","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}