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

Code before:
        webpage = self._download_webpage(url, video_id, fatal=False, note=f'Downloading {client} config')
        return self.extract_ytcfg(video_id, webpage) or {}

    def _extract_player_responses(self, clients, video_id, webpage, master_ytcfg, player_url, identity_token):
        initial_pr = None
        if webpage:
            initial_pr = self._extract_yt_initial_variable(
                webpage, self._YT_INITIAL_PLAYER_RESPONSE_RE,
                video_id, 'initial player response')

        original_clients = clients
        clients = clients[::-1]
        while clients:
            client = clients.pop()
            player_ytcfg = master_ytcfg if client == 'web' else {}
            if 'configs' not in self._configuration_arg('player_skip'):
                player_ytcfg = self._extract_player_ytcfg(client, video_id) or player_ytcfg

            pr = (
                initial_pr if client == 'web' and initial_pr
                else self._extract_player_response(
                    client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, identity_token, player_url, initial_pr))
            if pr:
                yield pr

            def append_client(client_name):
                if client_name in INNERTUBE_CLIENTS and client_name not in original_clients:
                    clients.append(client_name)

            # _creator clients can bypass AGE_VERIFICATION_REQUIRED if logged in
            if client.endswith("_agegate") and self._is_unplayable(pr) and self._generate_sapisidhash_header() is not None:
                append_client(client.replace("_agegate", "_creator"))
            elif self._is_agegated(pr):
                append_client(f'{client}_agegate')

        # Android player_response does not have microFormats which are needed for
        # extraction of some data. So we return the initial_pr with formats
        # stripped out even if not requested by the user
        # See: https://github.com/yt-dlp/yt-dlp/issues/501
        if initial_pr and 'web' not in original_clients:
            initial_pr['streamingData'] = None
            yield initial_pr

    def _extract_formats(self, streaming_data, video_id, player_url, is_live):
        itags, stream_ids = [], []
        itag_qualities, res_qualities = {}, {}
        q = qualities([
            # Normally tiny is the smallest video-only formats. But
            # audio-only formats with unknown quality may get tagged as tiny
            'tiny',
            'audio_quality_ultralow', 'audio_quality_low', 'audio_quality_medium', 'audio_quality_high',  # Audio only formats

Review comment (bug):
That's how it works for me. Just a suggestion. Try creator client if age-gate bypass fails with `UNPLAYABLE` and cookies are given. (3.)

Non embeddable video without Cookies
```
[youtube] Cr381pDsSsA: Downloading webpage
[youtube] Cr381pDsSsA: Downloading android player API JSON
[youtube] Cr381pDsSsA: Downloading android agegate player API JSON
[youtube] Cr381pDsSsA: Downloading web agegate player API JSON
ERROR: Playback on other websites has been disabled by the video owner.
```

Non embeddable video + Cookies
```
[youtube] Cr381pDsSsA: Downloading webpage
[youtube] Cr381pDsSsA: Downloading android player API JSON
[youtube] Cr381pDsSsA: Downloading android agegate player API JSON
[youtube] Cr381pDsSsA: Downloading android creator player API JSON
[youtube] Cr381pDsSsA: Downloading web agegate player API JSON
[youtube] Cr381pDsSsA: Downloading web creator player API JSON
[info] Cr381pDsSsA: Downloading 1 format(s): 22
```

Embeddable video without Cookies:
```
[youtube] 7t0SqerlBA0: Downloading webpage
[youtube] 7t0SqerlBA0: Downloading android player API JSON
[youtube] 7t0SqerlBA0: Downloading android agegate player API JSON
[youtube] 7t0SqerlBA0: Downloading web agegate player API JSON
[info] 7t0SqerlBA0: Downloading 1 format(s): 22
```

Code after:
        webpage = self._download_webpage(url, video_id, fatal=False, note=f'Downloading {client} config')
        return self.extract_ytcfg(video_id, webpage) or {}

    def _extract_player_responses(self, clients, video_id, webpage, master_ytcfg, player_url, identity_token):
        initial_pr = None
        if webpage:
            initial_pr = self._extract_yt_initial_variable(
                webpage, self._YT_INITIAL_PLAYER_RESPONSE_RE,
                video_id, 'initial player response')

        original_clients = clients
        clients = clients[::-1]

        def append_client(client_name):
            if client_name in INNERTUBE_CLIENTS and client_name not in original_clients:
                clients.append(client_name)

        while clients:
            client = clients.pop()
            player_ytcfg = master_ytcfg if client == 'web' else {}
            if 'configs' not in self._configuration_arg('player_skip'):
                player_ytcfg = self._extract_player_ytcfg(client, video_id) or player_ytcfg

            pr = (
                initial_pr if client == 'web' and initial_pr
                else self._extract_player_response(
                    client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, identity_token, player_url, initial_pr))
            if pr:
                yield pr

            # creator clients can bypass AGE_VERIFICATION_REQUIRED if logged in
            if client.endswith('_agegate') and self._is_unplayable(pr) and self._generate_sapisidhash_header():
                append_client(client.replace('_agegate', '_creator'))
            elif self._is_agegated(pr):
                append_client(f'{client}_agegate')

        # Android player_response does not have microFormats which are needed for
        # extraction of some data. So we return the initial_pr with formats
        # stripped out even if not requested by the user
        # See: https://github.com/yt-dlp/yt-dlp/issues/501
        if initial_pr and 'web' not in original_clients:
            initial_pr['streamingData'] = None
            yield initial_pr

    def _extract_formats(self, streaming_data, video_id, player_url, is_live):
        itags, stream_ids = [], []
        itag_qualities, res_qualities = {}, {}
        q = qualities([
            # Normally tiny is the smallest video-only formats. But
            # audio-only formats with unknown quality may get tagged as tiny
            'tiny',