AlistGo/alist · error

sub playlist not found in master m3u8

Error message

sub playlist not found in master m3u8

What it means

While resolving an m3u8 video link, the driver parsed the master playlist (looking for a #EXT-X-STREAM-INF line followed by a URI, then falling back to the last non-comment, non-empty line) and found no candidate sub-playlist path at all. It cannot proceed to fetch the actual media segments.

Source

Thrown at drivers/139/util.go:1598

			if i+1 < len(lines) {
				subRelPath = strings.TrimSpace(lines[i+1])
				if strings.Contains(line, "1920x1080") {
					break
				}
			}
		}
	}
	if subRelPath == "" {
		for i := len(lines) - 1; i >= 0; i-- {
			line := strings.TrimSpace(lines[i])
			if line != "" && !strings.HasPrefix(line, "#") {
				subRelPath = line
				break
			}
		}
	}
	if subRelPath == "" {
		return "", fmt.Errorf("sub playlist not found in master m3u8")
	}

	// 3. Get sub-playlist content
	base, _ := url.Parse(masterURL)
	ref, _ := url.Parse(subRelPath)
	subURL := base.ResolveReference(ref).String()

	resp, err = client.R().SetHeaders(headers).Get(subURL)
	if err != nil {
		return "", err
	}
	subContent := resp.String()

	// 4. Resolve relative TS paths to absolute URLs
	subBase, _ := url.Parse(subURL)
	subLines := strings.Split(subContent, "\n")
	var finalLines []string
	for _, line := range subLines {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Retry after a short delay — transcode-not-ready playlists frequently become valid once processing finishes
  2. Dump the master m3u8 content (log masterURL and body) to confirm whether it is a valid master playlist, a media playlist, or an error page
  3. If it is already a media playlist (contains #EXTINF segments but no variants), resolve segment URLs directly instead of recursing; update OpenList or patch locally
  4. Check the token/Referer headers still match what the CDN expects

Example fix

// before
if subRelPath == "" {
	return "", fmt.Errorf("sub playlist not found in master m3u8")
}
// after (treat as media playlist when segments exist)
if subRelPath == "" {
	if strings.Contains(masterContent, "#EXTINF") {
		return masterURL, nil // already a media playlist
	}
	return "", fmt.Errorf("sub playlist not found in master m3u8")
}
Defensive patterns

Strategy: fallback

Validate before calling

// Validate the master playlist before searching for variants
lines := strings.Split(masterContent, "\n")
if !containsTag(lines, "#EXTM3U") {
	return fmt.Errorf("not a valid m3u8 playlist: %q", truncate(masterContent, 64))
}

Type guard

func isMasterPlaylist(content string) bool {
	return strings.Contains(content, "#EXT-X-STREAM-INF")
}

Try / catch

// Fall back to treating the URL as a direct media playlist
url, err := resolveSubPlaylist(master)
if err != nil && strings.Contains(err.Error(), "sub playlist not found") {
	if strings.Contains(master.content, "#EXTINF") {
		return masterURL, nil
	}
	return "", err
}

Prevention

When it happens

Trigger: getLink for a video returns an m3u8 whose content is only tags/comments (e.g. a bare #EXTM3U, an error playlist, or a media playlist already), or the body is HTML that happens to pass earlier checks, leaving subRelPath empty.

Common situations: Upstream CDN serving an error/empty playlist when the video transcode is not ready, token-expired playlists, or format changes in the 139 video preview API.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/2a2ca33e726d3c1f. Report an issue: GitHub.