Repository: streamlink/streamlink

Issue:
plugins.pandatv: No Playable Streams Found
### Checklist

- [X] This is a plugin issue and not a different kind of issue
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

Latest stable release

### Description

No Playable stream found with plugin

https://www.pandalive.co.kr/live/play/19178412 or any other link of a streamer that is online would work



### Debug log

```text
streamlink --loglevel debug https://www.pandalive.co.kr/live/play/19178412 best -o h:\testing1.ts
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.9.8
[cli][debug] Streamlink: 3.1.1
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(1.2.1)
[cli][debug] Arguments:
[cli][debug]  url=https://www.pandalive.co.kr/live/play/19178412
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --output=h:\testing1.ts
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin pandalive for URL https://www.pandalive.co.kr/live/play/19178412
error: No playable streams found on this URL: https://www.pandalive.co.kr/live/play/19178412
```



Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/pandalive.py b/src/streamlink/plugins/pandalive.py
--- a/src/streamlink/plugins/pandalive.py
+++ b/src/streamlink/plugins/pandalive.py
@@ -12,19 +12,11 @@
     r"https?://(?:www\.)?pandalive\.co\.kr/"
 ))
 class Pandalive(Plugin):
-    _room_id_re = re.compile(r"roomid\s*=\s*String\.fromCharCode\((.*)\)")
-
     def _get_streams(self):
+        re_media_code = re.compile(r"""routePath:\s*(["'])(\\u002F|/)live(\\u002F|/)play(\\u002F|/)(?P<id>\d+)\1""")
         media_code = self.session.http.get(self.url, schema=validate.Schema(
-            validate.parse_html(),
-            validate.xml_xpath_string(".//script[contains(text(), 'roomid')]/text()"),
-            validate.any(None, validate.all(
-                validate.transform(self._room_id_re.search),
-                validate.any(None, validate.all(
-                    validate.get(1),
-                    validate.transform(lambda s: "".join(map(lambda c: chr(int(c)), s.split(",")))),
-                )),
-            )),
+            validate.transform(re_media_code.search),
+            validate.any(None, validate.get("id"))
         ))
 
         if not media_code:
@@ -34,10 +26,14 @@ def _get_streams(self):
 
         json = self.session.http.post(
             "https://api.pandalive.co.kr/v1/live/play",
-            data={"action": "watch", "mediaCode": media_code},
+            data={
+                "action": "watch",
+                "userIdx": media_code
+            },
             schema=validate.Schema(
-                validate.parse_json(), {
-                    validate.optional("media"): {
+                validate.parse_json(),
+                {
+                    "media": {
                         "title": str,
                         "userId": str,
                         "userNick": str,
@@ -45,14 +41,20 @@ def _get_streams(self):
                         "isLive": bool,
                         "liveType": str,
                     },
-                    validate.optional("PlayList"): {
-                        "hls2": [{
+                    "PlayList": {
+                        validate.optional("hls"): [{
+                            "url": validate.url(),
+                        }],
+                        validate.optional("hls2"): [{
+                            "url": validate.url(),
+                        }],
+                        validate.optional("hls3"): [{
                             "url": validate.url(),
                         }],
                     },
                     "result": bool,
                     "message": str,
-                },
+                }
             )
         )
 
@@ -73,7 +75,12 @@ def _get_streams(self):
         self.author = f"{json['media']['userNick']} ({json['media']['userId']})"
         self.title = f"{json['media']['title']}"
 
-        return HLSStream.parse_variant_playlist(self.session, json["PlayList"]["hls2"][0]["url"])
+        playlist = json["PlayList"]
+        for key in ("hls", "hls2", "hls3"):
+            # use the first available HLS stream
+            if key in playlist and playlist[key]:
+                # all stream qualities share the same URL, so just use the first one
+                return HLSStream.parse_variant_playlist(self.session, playlist[key][0]["url"])
 
 
 __plugin__ = Pandalive


Test patch: