yt-dlp/yt-dlp · error · ExtractorError
This livestream has ended
Error message
This livestream has ended
What it means
Soop live extractor: the access-token API (player_live_api.php with bno/stream_type/type/quality/pwd) returned CHANNEL without an AID and RESULT == 0 -- the broadcast number is valid but the stream is over. Expected error; the usual follow-up is a replay VOD that appears later.
Source
Thrown at yt_dlp/extractor/afreecatv.py:399
if channel_info.get('BPWD') == 'Y' and password is None:
raise ExtractorError(
'This livestream is protected by a password, use the --video-password option',
expected=True)
token_info = traverse_obj(self._download_json(
self._LIVE_API_URL, broadcast_no, 'Downloading access token for stream',
'Unable to download access token for stream', data=urlencode_postdata(filter_dict({
'bno': broadcast_no,
'stream_type': 'common',
'type': 'aid',
'quality': 'master',
'pwd': password,
}))), ('CHANNEL', {dict})) or {}
aid = token_info.get('AID')
if not aid:
result = token_info.get('RESULT')
if result == 0:
raise ExtractorError('This livestream has ended', expected=True)
elif result == -6:
self.raise_login_required('This livestream is for subscribers only', method='password')
raise ExtractorError('Unable to extract access token')
formats = self._extract_formats(channel_info, broadcast_no, aid)
station_info = traverse_obj(self._download_json(
'https://st.sooplive.com/api/get_station_status.php', broadcast_no,
'Downloading channel metadata', 'Unable to download channel metadata',
query={'szBjId': broadcaster_id}, fatal=False), {dict}) or {}
return {
'id': broadcast_no,
'title': channel_info.get('TITLE') or station_info.get('station_title'),
'uploader': channel_info.get('BJNICK') or station_info.get('station_name'),
'uploader_id': broadcaster_id,
'timestamp': parse_iso8601(station_info.get('broad_start'), delimiter=' ', timezone=dt.timedelta(hours=9)),
'formats': formats,View on GitHub (pinned to 81ecd58b13)
Solutions
- Re-check the channel -- if the broadcaster restarted, a new BNO applies (re-run the original channel URL)
- Wait for the replay VOD to be published on vod.sooplive.com and extract that instead
- Treat as terminal for live extraction; do not retry the same BNO
Defensive patterns
Strategy: fallback
Try / catch
from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadError
try:
info = YoutubeDL().extract_info(live_url) # play.sooplive.com/<bid>
except DownloadError as e:
if 'This livestream has ended' in str(e):
# fall back: replay VODs surface on vod.sooplive.com; re-check later
info = try_replay_vod(broadcaster_id, delay_hours=1)
else:
raise Prevention
- Start recorders slightly before announced stream times to avoid the post-end race
- On 'ended', poll the broadcaster's VOD list for the replay instead of the live URL
When it happens
Trigger: Joining a sooplive livestream in the minutes after it ended: the channel step still resolved a BNO, but the token step no longer issues an AID for it.
Common situations: Race at stream end; polling scripts hammering an ended broadcast; replays not yet uploaded so nothing to fall back to.
Related errors
- The channel is not currently live
- This livestream is protected by a password, use the --video-
- Unable to login: {self.IE_NAME} said: {error}
- The VOD does not exist
- This VOD is private
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/b4fc47fe64444313.
Report an issue: GitHub.