Repository: yt-dlp/yt-dlp

Issue:
[mixch] mixch extractor is 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

_No response_

### Provide a description that is worded well enough to be understood

Since around noon Japan time today, the format and content of the content information that MixChannel's server responds to has been updated, and it is no longer possible to extract video information using yt-dlp (stable@2024.03.10).
Previously, the information was extracted from the JSON information in the script tag embedded in the page, but with this update, it seems that Server Side Rendering is no longer performed, and the information is retrieved by calling a different URL that is assembled from the path.

However, since I am not very familiar with python programming, I am not confident in writing proper fixes and proper test code, and I do not know the manner of submitting a pull request. I am not at all familiar with python programming, and I am not confident in writing proper fixes and proper test code.

For that reason, here is a patch of the changes I made


```diff
--- yt_dlp/extractor/mixch.py	2020-02-02 09:00:00
+++ yt_dlp/extractor/mixch.py	2024-03-26 17:15:25
@@ -25,10 +25,9 @@
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        webpage = self._download_webpage(f'https://mixch.tv/u/{video_id}/live', video_id)
+        webpage = self._download_webpage(f'https://mixch.tv/api-web/users/{video_id}/live', video_id)
 
-        initial_js_state = self._parse_json(self._search_regex(
-            r'(?m)^\s*window\.__INITIAL_JS_STATE__\s*=\s*(\{.+?\});\s*$', webpage, 'initial JS state'), video_id)
+        initial_js_state = self._parse_json(webpage, video_id)
         if not initial_js_state.get('liveInfo'):
             raise UserNotLive(video_id=video_id)
 
```

I would appreciate it if you could review it and if it looks OK, make a pull request based on a more appropriate manner and incorporate it.

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] 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', 'https://mixch.tv/u/17461187/live']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2024.03.10 from yt-dlp/yt-dlp [615a84447] (pip)
[debug] Python 3.12.2 (CPython arm64 64bit) - macOS-14.4-arm64-arm-64bit (OpenSSL 3.2.1 30 Jan 2024)
[debug] exe versions: ffmpeg 6.1.1 (setts), ffprobe 6.1.1, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.20.0, brotli-1.1.0, certifi-2024.02.02, mutagen-1.47.0, requests-2.31.0, sqlite3-3.45.2, urllib3-2.2.1, websockets-12.0
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets
[debug] Loaded 1803 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: stable@2024.03.10 from yt-dlp/yt-dlp
yt-dlp is up to date (stable@2024.03.10 from yt-dlp/yt-dlp)
[mixch] Extracting URL: https://mixch.tv/u/17461187/live
[mixch] 17461187: Downloading webpage
ERROR: [mixch] 17461187: Unable to extract initial JS state; 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 "/opt/homebrew/Cellar/yt-dlp/2024.03.10/libexec/lib/python3.12/site-packages/yt_dlp/extractor/common.py", line 732, in extract
    ie_result = self._real_extract(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/yt-dlp/2024.03.10/libexec/lib/python3.12/site-packages/yt_dlp/extractor/mixch.py", line 30, in _real_extract
    initial_js_state = self._parse_json(self._search_regex(
                                        ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/yt-dlp/2024.03.10/libexec/lib/python3.12/site-packages/yt_dlp/extractor/common.py", line 1280, in _search_regex
    raise RegexNotFoundError('Unable to extract %s' % _name)
```



Discussion:
use `self._download_json` instead of `self._download_webpage`+`self._parse_json`

Resolving patch:
diff --git a/yt_dlp/extractor/mixch.py b/yt_dlp/extractor/mixch.py
--- a/yt_dlp/extractor/mixch.py
+++ b/yt_dlp/extractor/mixch.py
@@ -1,6 +1,6 @@
 from .common import InfoExtractor
 from ..networking.exceptions import HTTPError
-from ..utils import ExtractorError, UserNotLive, url_or_none
+from ..utils import ExtractorError, UserNotLive, int_or_none, url_or_none
 from ..utils.traversal import traverse_obj
 
 
@@ -27,25 +27,23 @@ class MixchIE(InfoExtractor):
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        webpage = self._download_webpage(f'https://mixch.tv/u/{video_id}/live', video_id)
-
-        initial_js_state = self._parse_json(self._search_regex(
-            r'(?m)^\s*window\.__INITIAL_JS_STATE__\s*=\s*(\{.+?\});\s*$', webpage, 'initial JS state'), video_id)
-        if not initial_js_state.get('liveInfo'):
+        data = self._download_json(f'https://mixch.tv/api-web/users/{video_id}/live', video_id)
+        if not traverse_obj(data, ('liveInfo', {dict})):
             raise UserNotLive(video_id=video_id)
 
         return {
             'id': video_id,
-            'title': traverse_obj(initial_js_state, ('liveInfo', 'title')),
-            'comment_count': traverse_obj(initial_js_state, ('liveInfo', 'comments')),
-            'view_count': traverse_obj(initial_js_state, ('liveInfo', 'visitor')),
-            'timestamp': traverse_obj(initial_js_state, ('liveInfo', 'created')),
-            'uploader': traverse_obj(initial_js_state, ('broadcasterInfo', 'name')),
             'uploader_id': video_id,
+            **traverse_obj(data, {
+                'title': ('liveInfo', 'title', {str}),
+                'comment_count': ('liveInfo', 'comments', {int_or_none}),
+                'view_count': ('liveInfo', 'visitor', {int_or_none}),
+                'timestamp': ('liveInfo', 'created', {int_or_none}),
+                'uploader': ('broadcasterInfo', 'name', {str}),
+            }),
             'formats': [{
                 'format_id': 'hls',
-                'url': (traverse_obj(initial_js_state, ('liveInfo', 'hls'))
-                        or f'https://d1hd0ww6piyb43.cloudfront.net/hls/torte_{video_id}.m3u8'),
+                'url': data['liveInfo']['hls'],
                 'ext': 'mp4',
                 'protocol': 'm3u8',
             }],


Test patch: