Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/afreecatv.py (Python)

Code before:
            raise ExtractorError(
                'This livestream is protected by a password, use the --video-password option',
                expected=True)

        params = {'bno': broadcast_no, 'stream_type': 'common', 'type': 'aid', 'quality': 'master', 'pwd': password}

        aid_response = self._download_json(self._LIVE_API_URL, broadcast_no, data=urlencode_postdata(params),
                                           note='Downloading access token for stream',
                                           errnote='Unable to download access token for stream')
        aid = traverse_obj(aid_response, ('CHANNEL', 'AID'))
        if not aid:
            raise ExtractorError(f'Unable to extract AID from {aid_response}', expected=False)

        stream_base_url = channel_info.get('RMD') or 'https://livestream-manager.afreecatv.com'
        stream_info = self._download_json(f'{stream_base_url}/broad_stream_assign.html', broadcast_no, query={
            # works: gs_cdn_pc_app, gs_cdn_mobile_web, gs_cdn_pc_web
            'return_type': 'gs_cdn_pc_app',
            'broad_key': f'{broadcast_no}-common-master-hls',
        }, note='Downloading metadata for stream', errnote='Unable to download metadata for stream')

        station_info = self._download_json('https://st.afreecatv.com/api/get_station_status.php', broadcast_no,
                                           query={'szBjId': broadcaster_id}, note='Downloading channel metadata',
                                           errnote='Unable to download channel metadata')

        formats = self._extract_m3u8_formats(update_url_query(stream_info['view_url'], {'aid': aid}),
                                             broadcast_no, 'mp4', m3u8_id='hls', headers={'Referer': url})
        # Add Referer to download the segments
        for f in formats:
            f.setdefault('http_headers', {})['Referer'] = url

        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': unified_timestamp(station_info.get('broad_start')),
            'formats': formats,
            'is_live': True,
        }


class AfreecaTVUserIE(InfoExtractor):
    IE_NAME = 'afreecatv:user'
    _VALID_URL = r'https?://bj\.afreeca(?:tv)?\.com/(?P<id>[^/]+)/vods/?(?P<slug_type>[^/]+)?'
    _TESTS = [{
        'url': 'https://bj.afreecatv.com/ryuryu24/vods/review',
        'info_dict': {
            '_type': 'playlist',
            'id': 'ryuryu24',
            'title': 'ryuryu24 - review',
        },

Review comment (refactor):
let's flip-flop these two requests, since `formats` is fatal and `station_info` shouldn't be (it's metadata-only)
```suggestion
        formats = self._extract_m3u8_formats(
            stream_info['view_url'], broadcast_no, 'mp4', m3u8_id='hls',
            query={'aid': aid}, headers={'Referer': url})

        station_info = traverse_obj(self._download_json(
            'https://st.afreecatv.com/api/get_station_status.php', broadcast_no,
            'Downloading channel metadata', 'Unable to download channel metadata',
            query={'szBjId': broadcaster_id}, fatal=False), {dict}) or {}
```

Code after:
        if not broadcast_no:
            raise UserNotLive(video_id=broadcaster_id)

        password = self.get_param('videopassword')
        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)

        aid = 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']['AID']

        stream_base_url = channel_info.get('RMD') or 'https://livestream-manager.afreecatv.com'
        stream_info = self._download_json(f'{stream_base_url}/broad_stream_assign.html', broadcast_no, query={
            # works: gs_cdn_pc_app, gs_cdn_mobile_web, gs_cdn_pc_web
            'return_type': 'gs_cdn_pc_app',
            'broad_key': f'{broadcast_no}-common-master-hls',
        }, note='Downloading metadata for stream', errnote='Unable to download metadata for stream')

        formats = self._extract_m3u8_formats(
            stream_info['view_url'], broadcast_no, 'mp4', m3u8_id='hls',
            query={'aid': aid}, headers={'Referer': url})

        station_info = traverse_obj(self._download_json(
            'https://st.afreecatv.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': unified_timestamp(station_info.get('broad_start')),
            'formats': formats,
            'is_live': True,
            'http_headers': {'Referer': url},
        }


class AfreecaTVUserIE(InfoExtractor):
    IE_NAME = 'afreecatv:user'
    _VALID_URL = r'https?://bj\.afreeca(?:tv)?\.com/(?P<id>[^/]+)/vods/?(?P<slug_type>[^/]+)?'
    _TESTS = [{