Repository: yt-dlp/yt-dlp

Issue:
Twitch VOD downloads are broken
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that yt-dlp is broken on a **supported** site
- [X] I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

Denmark

### Provide a description that is worded well enough to be understood

I'm getting 400 Bad Request errors when downloading VODs from Twitch. Downloading clips work.
I'm on Gentoo Linux.
Command line command is: yt-dlp -vU  -o 1.mp4 'https://www.twitch.tv/videos/2025985859'

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', '-o', '1.mp4', 'https://www.twitch.tv/videos/2025985859']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version local@2024.01.08 [5af1f1978]
[debug] Python 3.11.7 (CPython x86_64 64bit) - Linux-6.7.0-rc8-x86_64-AMD_Ryzen_9_3900X_12-Core_Processor-with-glibc2.38 (OpenSSL 3.1.4 24 Oct 2023, glibc 2.38)
[debug] exe versions: ffmpeg 6.1.1 (fdk,setts), ffprobe 6.1.1
[debug] Optional libraries: brotli-1.1.0, certifi-3021.03.16, pycrypto-3.19.1, requests-2.31.0, sqlite3-3.44.2, urllib3-2.1.0
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests
[debug] Loaded 1798 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: stable@2023.12.30 from yt-dlp/yt-dlp
yt-dlp is up to date (local@2024.01.08)
[twitch:vod] Extracting URL: https://www.twitch.tv/videos/2025985859
[twitch:vod] 2025985859: Downloading stream metadata GraphQL
[twitch:vod] 2025985859: Downloading video access token GraphQL
[twitch:vod] 2025985859: Downloading m3u8 information
ERROR: [twitch:vod] 2025985859: Failed to download m3u8 information: HTTP Error 400: Bad Request (caused by <HTTPError 400: Bad Request>); please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 718, in extract
    ie_result = self._real_extract(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/twitch.py", line 535, in _real_extract
    formats = self._extract_m3u8_formats(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 1946, in _extract_m3u8_formats
    fmts, subs = self._extract_m3u8_formats_and_subtitles(*args, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 1968, in _extract_m3u8_formats_and_subtitles
    res = self._download_webpage_handle(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 906, in _download_webpage_handle
    urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal, data=data, headers=headers, query=query, expected_status=expected_status)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 863, in _request_webpage
    raise ExtractorError(errmsg, cause=err)

  File "/usr/lib/python3.11/site-packages/yt_dlp/YoutubeDL.py", line 4082, in urlopen
    return self._request_director.send(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/common.py", line 114, in send
    response = handler.send(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/_helper.py", line 204, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/common.py", line 325, in send
    return self._send(request)
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/_requests.py", line 343, in _send
    raise HTTPError(res, redirect_loop=max_redirects_exceeded)
yt_dlp.networking.exceptions.HTTPError: HTTP Error 400: Bad Request

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 850, in _request_webpage
    return self._downloader.urlopen(self._create_request(url_or_request, data, headers, query))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/YoutubeDL.py", line 4114, in urlopen
    raise _CompatHTTPError(e) from e
yt_dlp.networking.exceptions._CompatHTTPError: HTTP Error 400: Bad Request
```



Discussion:
Same issue on my Windows 10, Docker W/Ubuntu 22.04 and Machintosh machines. Also tried with ffmpeg as external downloader: no luck
I can't reproduce the issue. May be a regional thing (and hopefully temporary?)
If someone experiencing the issue wants to add `--print-traffic` to their command and post the log, that could be useful (but do NOT do this if you are passing cookies or credentials to yt-dlp)
```
[debug] Command-line config: ['-vU', '--print-traffic', '-o', '1.mp4', 'https://www.twitch.tv/videos/2025985859']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version local@2024.01.08 [5af1f1978]
[debug] Python 3.11.7 (CPython x86_64 64bit) - Linux-6.7.0-rc8-x86_64-AMD_Ryzen_9_3900X_12-Core_Processor-with-glibc2.38 (OpenSSL 3.1.4 24 Oct 2023, glibc 2.38)
[debug] exe versions: ffmpeg 6.1.1 (fdk,setts), ffprobe 6.1.1
[debug] Optional libraries: brotli-1.1.0, certifi-3021.03.16, pycrypto-3.19.1, requests-2.31.0, sqlite3-3.44.2, urllib3-2.1.0
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests
[debug] Loaded 1798 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
director: Handler preferences for this request: urllib=0, requests=100
director: Checking if "requests" supports this request.
director: Sending request via "requests"
requests: Starting new HTTPS connection (1): api.github.com:443
send: b'GET /repos/yt-dlp/yt-dlp/releases/latest HTTP/1.1\r\nHost: api.github.com\r\nConnection: keep-alive\r\nUser-Agent: yt-dlp\r\nAccept: application/vnd.github+json\r\nAccept-Language: en-us,en;q=0.5\r\nSec-Fetch-Mode: navigate\r\nX-Github-Api-Version: 2022-11-28\r\nAccept-Encoding: gzip, deflate, br\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: GitHub.com
header: Date: Mon, 08 Jan 2024 21:15:31 GMT
header: Cache-Control: public, max-age=60, s-maxage=60
header: Vary: Accept, Accept-Encoding, Accept, X-Requested-With
header: ETag: "762c7b088738998177db0230b03e6566decf043929f1f98a651a56b592d88fc6"
header: Last-Modified: Sat, 30 Dec 2023 21:59:27 GMT
header: x-github-api-version-selected: 2022-11-28
header: Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
header: Access-Control-Allow-Origin: *
header: Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
header: X-Frame-Options: deny
header: X-Content-Type-Options: nosniff
header: X-XSS-Protection: 0
header: Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
header: Content-Security-Policy: default-src 'none'
header: Content-Type: application/json; charset=utf-8
header: X-GitHub-Media-Type: github.v3; format=json
header: Content-Encoding: gzip
header: X-RateLimit-Limit: 60
header: X-RateLimit-Remaining: 53
header: X-RateLimit-Reset: 1704749051
header: X-RateLimit-Resource: core
header: X-RateLimit-Used: 7
header: Accept-Ranges: bytes
header: Content-Length: 7572
header: X-GitHub-Request-Id: A838:266E07:1DB2D68:1DF7D10:659C65F9
Latest version: stable@2023.12.30 from yt-dlp/yt-dlp
yt-dlp is up to date (local@2024.01.08)
[twitch:vod] Extracting URL: https://www.twitch.tv/videos/2025985859
[twitch:vod] 2025985859: Downloading stream metadata GraphQL
director: Handler preferences for this request: urllib=0, requests=100
director: Checking if "requests" supports this request.
director: Sending request via "requests"
requests: Starting new HTTPS connection (1): gql.twitch.tv:443
send: b'POST /gql HTTP/1.1\r\nHost: gql.twitch.tv\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nSec-Fetch-Mode: navigate\r\nContent-Type: text/plain;charset=UTF-8\r\nClient-Id: ue6666qo983tsx6so1t0vnawi233wa\r\nAccept-Encoding: gzip, deflate, br\r\nContent-Length: 738\r\n\r\n'
send: b'[{"operationName": "VideoMetadata", "variables": {"channelLogin": "", "videoID": "2025985859"}, "extensions": {"persistedQuery": {"version": 1, "sha256Hash": "49b5b8f268cdeb259d75b58dcb0c1a748e3b575003448a2333dc5cdafd49adad"}}}, {"operationName": "VideoPlayer_ChapterSelectButtonVideo", "variables": {"includePrivate": false, "videoID": "2025985859"}, "extensions": {"persistedQuery": {"version": 1, "sha256Hash": "8d2793384aac3773beab5e59bd5d6f585aedb923d292800119e03d40cd0f9b41"}}}, {"operationName": "VideoPlayer_VODSeekbarPreviewVideo", "variables": {"includePrivate": false, "videoID": "2025985859"}, "extensions": {"persistedQuery": {"version": 1, "sha256Hash": "07e99e4d56c5a7c67117a154777b0baf85a5ffefa393b213f4bc712ccaf85dd6"}}}]'
reply: 'HTTP/1.1 200 OK\r\n'
header: Connection: keep-alive
header: Content-Length: 776
header: Content-Type: application/json
header: Access-Control-Allow-Origin: *
header: Content-Encoding: br
header: Date: Mon, 08 Jan 2024 21:15:38 GMT
[twitch:vod] 2025985859: Downloading video access token GraphQL
director: Handler preferences for this request: urllib=0, requests=100
director: Checking if "requests" supports this request.
director: Sending request via "requests"
send: b'POST /gql HTTP/1.1\r\nHost: gql.twitch.tv\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nSec-Fetch-Mode: navigate\r\nContent-Type: text/plain;charset=UTF-8\r\nClient-Id: ue6666qo983tsx6so1t0vnawi233wa\r\nAccept-Encoding: gzip, deflate, br\r\nContent-Length: 383\r\n\r\n'
send: b'{"query": "{\\n              videoPlaybackAccessToken(\\n                id: \\"2025985859\\",\\n                params: {\\n                  platform: \\"web\\",\\n                  playerBackend: \\"mediaplayer\\",\\n                  playerType: \\"site\\"\\n                }\\n              )\\n              {\\n                value\\n                signature\\n              }\\n            }"}'
reply: 'HTTP/1.1 200 OK\r\n'
header: Connection: keep-alive
header: Content-Length: 289
header: Content-Type: application/json
header: Access-Control-Allow-Origin: *
header: Content-Encoding: br
header: Date: Mon, 08 Jan 2024 21:15:38 GMT
[twitch:vod] 2025985859: Downloading m3u8 information
director: Handler preferences for this request: urllib=0, requests=100
director: Checking if "requests" supports this request.
director: Sending request via "requests"
requests: Starting new HTTPS connection (1): usher.ttvnw.net:443
send: b'GET /vod/2025985859.m3u8?allow_source=true&allow_audio_only=true&allow_spectre=true&player=twitchweb&playlist_include_framerate=true&nauth=%7B%22authorization%22%3A%7B%22forbidden%22%3Afalse%2C%22reason%22%3A%22%22%7D%2C%22chansub%22%3A%7B%22restricted_bitrates%22%3A%5B%5D%7D%2C%22device_id%22%3Anull%2C%22expires%22%3A1704820538%2C%22https_required%22%3Atrue%2C%22privileged%22%3Afalse%2C%22user_id%22%3Anull%2C%22version%22%3A2%2C%22vod_id%22%3A2025985859%7D&nauthsig=81b37cdd3b32eb14a78d8d97860b34939d555506 HTTP/1.1\r\nHost: usher.ttvnw.net\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nSec-Fetch-Mode: navigate\r\nAccept-Encoding: gzip, deflate, br\r\n\r\n'
reply: 'HTTP/1.1 400 Bad Request\r\n'
header: Content-Type: text/plain; charset=utf-8
header: Content-Length: 12
header: Connection: keep-alive
header: Date: Mon, 08 Jan 2024 21:15:38 GMT
header: X-Amzn-Trace-Id: Root=1-659c65fa-537ad3d448ff703462789e87
header: X-Content-Type-Options: nosniff
header: X-Cache: Error from cloudfront
header: Via: 1.1 9eb1edfe46aec057cf2bf91f98376e78.cloudfront.net (CloudFront)
header: X-Amz-Cf-Pop: CPH50-C2
header: X-Amz-Cf-Id: 7ZO74qJOeeN3WTY-VAptPJfo0arl7CLtJ4jhj0HFPYTaVBFJ2SUJ8g==
ERROR: [twitch:vod] 2025985859: Failed to download m3u8 information: HTTP Error 400: Bad Request (caused by <HTTPError 400: Bad Request>); please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 718, in extract
    ie_result = self._real_extract(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/twitch.py", line 535, in _real_extract
    formats = self._extract_m3u8_formats(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 1946, in _extract_m3u8_formats
    fmts, subs = self._extract_m3u8_formats_and_subtitles(*args, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 1968, in _extract_m3u8_formats_and_subtitles
    res = self._download_webpage_handle(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 906, in _download_webpage_handle
    urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal, data=data, headers=headers, query=query, expected_status=expected_status)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 863, in _request_webpage
    raise ExtractorError(errmsg, cause=err)

  File "/usr/lib/python3.11/site-packages/yt_dlp/YoutubeDL.py", line 4082, in urlopen
    return self._request_director.send(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/common.py", line 114, in send
    response = handler.send(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/_helper.py", line 204, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/common.py", line 325, in send
    return self._send(request)
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/networking/_requests.py", line 343, in _send
    raise HTTPError(res, redirect_loop=max_redirects_exceeded)
yt_dlp.networking.exceptions.HTTPError: HTTP Error 400: Bad Request

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/yt_dlp/extractor/common.py", line 850, in _request_webpage
    return self._downloader.urlopen(self._create_request(url_or_request, data, headers, query))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/yt_dlp/YoutubeDL.py", line 4114, in urlopen
    raise _CompatHTTPError(e) from e
yt_dlp.networking.exceptions._CompatHTTPError: HTTP Error 400: Bad Request

```
> I can't reproduce the issue. May be a regional thing (and hopefully temporary?)

I can reproduce both the HTTP 400 and the proper function depending on the traffic egress country. When running the above command from Germany (direct), Sweden or Italy (via VPN), I get the same error. When running from Brazil, UK or US (again via VPN), the download works as usual.
facing the same exact issue here too

VPNing to UK works tho
Tried with a US VPN: It works 

Resolving patch:
diff --git a/yt_dlp/extractor/twitch.py b/yt_dlp/extractor/twitch.py
--- a/yt_dlp/extractor/twitch.py
+++ b/yt_dlp/extractor/twitch.py
@@ -8,7 +8,6 @@
 from ..compat import (
     compat_parse_qs,
     compat_str,
-    compat_urllib_parse_urlencode,
     compat_urllib_parse_urlparse,
 )
 from ..utils import (
@@ -191,6 +190,20 @@ def _get_thumbnails(self, thumbnail):
             'url': thumbnail,
         }] if thumbnail else None
 
+    def _extract_twitch_m3u8_formats(self, video_id, token, signature):
+        """Subclasses must define _M3U8_PATH"""
+        return self._extract_m3u8_formats(
+            f'{self._USHER_BASE}/{self._M3U8_PATH}/{video_id}.m3u8', video_id, 'mp4', query={
+                'allow_source': 'true',
+                'allow_audio_only': 'true',
+                'allow_spectre': 'true',
+                'p': random.randint(1000000, 10000000),
+                'player': 'twitchweb',
+                'playlist_include_framerate': 'true',
+                'sig': signature,
+                'token': token,
+            })
+
 
 class TwitchVodIE(TwitchBaseIE):
     IE_NAME = 'twitch:vod'
@@ -203,6 +216,7 @@ class TwitchVodIE(TwitchBaseIE):
                         )
                         (?P<id>\d+)
                     '''
+    _M3U8_PATH = 'vod'
 
     _TESTS = [{
         'url': 'http://www.twitch.tv/riotgames/v/6528877?t=5m10s',
@@ -532,20 +546,8 @@ def _real_extract(self, url):
         info = self._extract_info_gql(video, vod_id)
         access_token = self._download_access_token(vod_id, 'video', 'id')
 
-        formats = self._extract_m3u8_formats(
-            '%s/vod/%s.m3u8?%s' % (
-                self._USHER_BASE, vod_id,
-                compat_urllib_parse_urlencode({
-                    'allow_source': 'true',
-                    'allow_audio_only': 'true',
-                    'allow_spectre': 'true',
-                    'player': 'twitchweb',
-                    'playlist_include_framerate': 'true',
-                    'nauth': access_token['value'],
-                    'nauthsig': access_token['signature'],
-                })),
-            vod_id, 'mp4', entry_protocol='m3u8_native')
-
+        formats = self._extract_twitch_m3u8_formats(
+            vod_id, access_token['value'], access_token['signature'])
         formats.extend(self._extract_storyboard(vod_id, video.get('storyboard'), info.get('duration')))
 
         self._prefer_source(formats)
@@ -924,6 +926,7 @@ class TwitchStreamIE(TwitchBaseIE):
                         )
                         (?P<id>[^/#?]+)
                     '''
+    _M3U8_PATH = 'api/channel/hls'
 
     _TESTS = [{
         'url': 'http://www.twitch.tv/shroomztv',
@@ -1026,23 +1029,10 @@ def _real_extract(self, url):
 
         access_token = self._download_access_token(
             channel_name, 'stream', 'channelName')
-        token = access_token['value']
 
         stream_id = stream.get('id') or channel_name
-        query = {
-            'allow_source': 'true',
-            'allow_audio_only': 'true',
-            'allow_spectre': 'true',
-            'p': random.randint(1000000, 10000000),
-            'player': 'twitchweb',
-            'playlist_include_framerate': 'true',
-            'segment_preference': '4',
-            'sig': access_token['signature'].encode('utf-8'),
-            'token': token.encode('utf-8'),
-        }
-        formats = self._extract_m3u8_formats(
-            '%s/api/channel/hls/%s.m3u8' % (self._USHER_BASE, channel_name),
-            stream_id, 'mp4', query=query)
+        formats = self._extract_twitch_m3u8_formats(
+            channel_name, access_token['value'], access_token['signature'])
         self._prefer_source(formats)
 
         view_count = stream.get('viewers')


Test patch: