{"record":{"id":"878f473e4742d95a","repo":"agalwood/Motrix","slug":"unsupported-master-878f47","errorCode":"unsupported-master","errorMessage":"master playlist has no variants","messagePattern":"master playlist has no variants","errorType":"error_code","errorClass":"MediaParseError","httpStatus":null,"severity":"error","filePath":"src/core/media/hls-parser.ts","lineNumber":98,"sourceCode":"  const variants: Variant[] = []\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i]\n    if (!line?.startsWith('#EXT-X-STREAM-INF:')) continue\n    const attrs = line.slice('#EXT-X-STREAM-INF:'.length)\n    const bandwidth = Number(attr(attrs, 'BANDWIDTH') ?? '0')\n    const audioGroup = attr(attrs, 'AUDIO')\n    const uriLine = lines[i + 1]?.trim()\n    if (uriLine && !uriLine.startsWith('#')) {\n      variants.push({\n        url: resolveUri(url, uriLine),\n        bandwidth,\n        audioGroup,\n      })\n    }\n  }\n\n  if (variants.length === 0) {\n    throw new MediaParseError(\n      'unsupported-master',\n      'master playlist has no variants'\n    )\n  }\n\n  const best = variants.reduce((a, b) => (b.bandwidth > a.bandwidth ? b : a))\n  const audioUrl =\n    best.audioGroup != null ? audioRenditions.get(best.audioGroup) : undefined\n\n  return { variantUrl: best.url, ...(audioUrl ? { audioUrl } : {}) }\n}\n\n// ---------------------------------------------------------------------------\n// parseHlsMedia\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a media (non-master) HLS playlist into a SegmentPlan.","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/media/hls-parser.ts#L80-L116","documentation":"MediaParseError with code 'unsupported-master', thrown by parseHlsMaster when no #EXT-X-STREAM-INF:bgtag line with a following non-comment URI line was found. A master playlist must enumerate at least one variant; otherwise there is nothing to play and the master is malformed or it is actually a media playlist fed to the wrong parser.","triggerScenarios":"Calling parseHlsMaster on a media (non-master) playlist that contains #EXTINF segments instead of #EXT-X-STREAM-INF; on a master whose variant URI lines are missing or all commented; on an empty or stub playlist; on a playlist whose #EXT-X-STREAM-INF line is the last line (no following URI).","commonSituations":"URL-dispatch bug: a media playlist URL handed to the master parser; playlists generated by a misconfigured encoder that emitted the header but no variants; trailing-newline issues where the URI line is consumed by line-splitting.","solutions":["Confirm the URL points at a master playlist (contains #EXT-X-STREAM-INF).","If the URL is actually a media playlist, call parseHlsMedia instead.","Auto-detect: if text.includes('#EXT-X-STREAM-INF') use parseHlsMaster, else if text.includes('#EXTINF') use parseHlsMedia.","Inspect the raw playlist text to ensure each #EXT-X-STREAM-INF has a URI on the immediately following non-blank, non-# line."],"exampleFix":"// before\nconst { variantUrl } = parseHlsMaster(text, url)\n// after — detect playlist role first\nif (text.includes('#EXT-X-STREAM-INF')) {\n  const { variantUrl } = parseHlsMaster(text, url)\n} else if (text.includes('#EXTINF')) {\n  const plan = parseHlsMedia(text, url)\n} else {\n  throw new Error('Unrecognised HLS playlist shape')\n}","handlingStrategy":"validation","validationCode":"function isHlsMasterPlaylist(text: string): boolean {\n  return text.includes('#EXT-X-STREAM-INF')\n}\nif (!isHlsMasterPlaylist(text)) {\n  if (text.includes('#EXTINF')) {\n    // call parseHlsMedia instead\n  } else {\n    throw new Error('Text is not a recognised HLS playlist.')\n  }\n}","typeGuard":"function isHlsMasterPlaylist(text: string): boolean {\n  return text.includes('#EXT-X-STREAM-INF')\n}","tryCatchPattern":"try {\n  const { variantUrl } = parseHlsMaster(text, url)\n} catch (e) {\n  if (e instanceof MediaParseError && e.code === 'unsupported-master') {\n    if (text.includes('#EXTINF')) {\n      // it was actually a media playlist — call parseHlsMedia\n    }\n  } else throw e\n}","preventionTips":["Detect playlist role (#EXT-X-STREAM-INF vs #EXTINF) before dispatching.","Validate that each #EXT-X-STREAM-INF has a following URI line when ingesting user URLs.","Keep URL-to-role metadata if you control the asset catalogue."],"tags":["hls","media","streaming","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}