agalwood/Motrix · error · MediaParseError

unsupported-master

unsupported-master

Error message

master playlist has no variants

What it means

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.

Source

Thrown at src/core/media/hls-parser.ts:98

  const variants: Variant[] = []
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    if (!line?.startsWith('#EXT-X-STREAM-INF:')) continue
    const attrs = line.slice('#EXT-X-STREAM-INF:'.length)
    const bandwidth = Number(attr(attrs, 'BANDWIDTH') ?? '0')
    const audioGroup = attr(attrs, 'AUDIO')
    const uriLine = lines[i + 1]?.trim()
    if (uriLine && !uriLine.startsWith('#')) {
      variants.push({
        url: resolveUri(url, uriLine),
        bandwidth,
        audioGroup,
      })
    }
  }

  if (variants.length === 0) {
    throw new MediaParseError(
      'unsupported-master',
      'master playlist has no variants'
    )
  }

  const best = variants.reduce((a, b) => (b.bandwidth > a.bandwidth ? b : a))
  const audioUrl =
    best.audioGroup != null ? audioRenditions.get(best.audioGroup) : undefined

  return { variantUrl: best.url, ...(audioUrl ? { audioUrl } : {}) }
}

// ---------------------------------------------------------------------------
// parseHlsMedia
// ---------------------------------------------------------------------------

/**
 * Parse a media (non-master) HLS playlist into a SegmentPlan.

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Confirm the URL points at a master playlist (contains #EXT-X-STREAM-INF).
  2. If the URL is actually a media playlist, call parseHlsMedia instead.
  3. Auto-detect: if text.includes('#EXT-X-STREAM-INF') use parseHlsMaster, else if text.includes('#EXTINF') use parseHlsMedia.
  4. Inspect the raw playlist text to ensure each #EXT-X-STREAM-INF has a URI on the immediately following non-blank, non-# line.

Example fix

// before
const { variantUrl } = parseHlsMaster(text, url)
// after — detect playlist role first
if (text.includes('#EXT-X-STREAM-INF')) {
  const { variantUrl } = parseHlsMaster(text, url)
} else if (text.includes('#EXTINF')) {
  const plan = parseHlsMedia(text, url)
} else {
  throw new Error('Unrecognised HLS playlist shape')
}
Defensive patterns

Strategy: validation

Validate before calling

function isHlsMasterPlaylist(text: string): boolean {
  return text.includes('#EXT-X-STREAM-INF')
}
if (!isHlsMasterPlaylist(text)) {
  if (text.includes('#EXTINF')) {
    // call parseHlsMedia instead
  } else {
    throw new Error('Text is not a recognised HLS playlist.')
  }
}

Type guard

function isHlsMasterPlaylist(text: string): boolean {
  return text.includes('#EXT-X-STREAM-INF')
}

Try / catch

try {
  const { variantUrl } = parseHlsMaster(text, url)
} catch (e) {
  if (e instanceof MediaParseError && e.code === 'unsupported-master') {
    if (text.includes('#EXTINF')) {
      // it was actually a media playlist — call parseHlsMedia
    }
  } else throw e
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/878f473e4742d95a. Report an issue: GitHub.