yt-dlp/yt-dlp · error · ExtractorError

Failed to get m3u8 playlist

Error message

Failed to get m3u8 playlist

What it means

Raised by TwitCastingIE when the movie playlist yields no m3u8 URLs and the websocket-fragment path produced no formats either (that path raises a login error first when empty). It means no playable HLS manifest could be located for the movie, typically because the stream data is not exposed anonymously. Raised without expected=True, so it is treated as an extraction failure.

Source

Thrown at yt_dlp/extractor/twitcasting.py:218

                    formats.append({
                        'url': update_url_query(ws_url, password_params),
                        'format_id': f'ws-{mode}',
                        'ext': 'mp4',
                        'quality': qq(mode),
                        'source_preference': -10,
                        # TwitCasting simply sends moof atom directly over WS
                        'protocol': 'websocket_frag',
                    })

            if not formats:
                self.raise_login_required()

            infodict = {
                'formats': formats,
                '_format_sort_fields': ('source', ),
            }
        elif not m3u8_urls:
            raise ExtractorError('Failed to get m3u8 playlist')
        elif len(m3u8_urls) == 1:
            formats = self._extract_m3u8_formats(
                m3u8_urls[0], video_id, 'mp4', headers=self._M3U8_HEADERS)
            infodict = {
                # No problem here since there's only one manifest
                'formats': formats,
                'http_headers': self._M3U8_HEADERS,
            }
        else:
            infodict = {
                '_type': 'multi_video',
                'entries': [{
                    'id': f'{video_id}-{num}',
                    'url': m3u8_url,
                    'ext': 'mp4',
                    # Requesting the manifests here will cause download to fail.
                    # So use ffmpeg instead. See: https://github.com/yt-dlp/yt-dlp/issues/382
                    'protocol': 'm3u8',

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. If the stream is members-only, log in via --username/--password or --cookies and retry.
  2. Confirm the movie page shows a playable recording in a browser.
  3. Update yt-dlp; playlist parsing was adjusted several times after TwitCasting markup changes.
  4. For recent recordings, retry once processing finishes.
Defensive patterns

Strategy: try-catch

Type guard

def is_login_or_no_formats(e: Exception) -> bool:
    return isinstance(e, LoginRequired) or (isinstance(e, ExtractorError) and 'm3u8 playlist' in str(e))

Try / catch

from yt_dlp.utils import ExtractorError, LoginRequired

try:
    info = ydl.extract_info(url, download=False)
except LoginRequired:
    ...  # websocket path found nothing; members-only content
except ExtractorError as e:
    if 'Failed to get m3u8 playlist' in str(e):
        ...  # no anonymous HLS; retry with credentials or skip

Prevention

When it happens

Trigger: data-movie-playlist JSON contains no HLS bitrates (empty m3u8_urls) and the WebSocket pass yields nothing because the video is members-only or the playlist shape changed, falling into the 'elif not m3u8_urls' branch.

Common situations: Members-only (paid community) streams without login, deleted videos whose playlist is emptied, recordings still processing, or a TwitCasting markup change that hides the playlist attribute.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/5b17e3a33955b026. Report an issue: GitHub.