We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
plugins.Pluto: Freezes at commercials and plays no audio on TV Shows
### Checklist

- [X] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
- [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 build from the master branch

### Description

Hello! Pluto TV plugin have problems. The plugin plays fine, but it freezes at commercials and some TV shows don't have commercials, it plays no audio because it thinks it at a commercial. This happens on all Pluto TV channels.

### Debug log

```text
C:\Windows\system32>streamlink -l debug https://pluto.tv/live-tv/forever-kids best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.11.5
[cli][debug] Streamlink: 6.1.0
[cli][debug] Dependencies:
[cli][debug]  certifi: 2023.7.22
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.3
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.18.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.22.2
[cli][debug]  trio-websocket: 0.10.3
[cli][debug]  typing-extensions: 4.7.1
[cli][debug]  urllib3: 2.0.4
[cli][debug]  websocket-client: 1.6.1
[cli][debug] Arguments:
[cli][debug]  url=https://pluto.tv/live-tv/forever-kids
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin pluto for URL https://pluto.tv/live-tv/forever-kids
[plugins.pluto][debug] slug=forever-kids
[plugins.pluto][debug] app_version=7.7.0-18f7ab32608969ea5bcbce8d0e23b9d0e1b24717
[stream.hls][warning] Encountered a stream discontinuity. This is unsupported and will result in incoherent output data.
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/streamlink/plugins/pluto.py`
Content:
```
1 """
2 $description Live TV and video on-demand service owned by Paramount Streaming.
3 $url pluto.tv
4 $type live, vod
5 $metadata id
6 $metadata author
7 $metadata category
8 $metadata title
9 """
10 
11 import logging
12 import re
13 from urllib.parse import parse_qs, urljoin
14 from uuid import uuid4
15 
16 from streamlink.plugin import Plugin, pluginmatcher
17 from streamlink.plugin.api import validate
18 from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter
19 from streamlink.utils.url import update_qsd
20 
21 
22 log = logging.getLogger(__name__)
23 
24 
25 class PlutoHLSStreamWriter(HLSStreamWriter):
26     ad_re = re.compile(r"_ad/creative/|dai\.google\.com|Pluto_TV_OandO/.*Bumper")
27 
28     def should_filter_sequence(self, sequence):
29         return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)
30 
31 
32 class PlutoHLSStreamReader(HLSStreamReader):
33     __writer__ = PlutoHLSStreamWriter
34 
35 
36 class PlutoHLSStream(HLSStream):
37     __shortname__ = "hls-pluto"
38     __reader__ = PlutoHLSStreamReader
39 
40 
41 @pluginmatcher(re.compile(r"""
42     https?://(?:www\.)?pluto\.tv/(?:\w{2}/)?(?:
43         live-tv/(?P<slug_live>[^/]+)
44         |
45         on-demand/series/(?P<slug_series>[^/]+)(?:/season/\d+)?/episode/(?P<slug_episode>[^/]+)
46         |
47         on-demand/movies/(?P<slug_movies>[^/]+)
48     )/?$
49 """, re.VERBOSE))
50 class Pluto(Plugin):
51     def _get_api_data(self, kind, slug, slugfilter=None):
52         log.debug(f"slug={slug}")
53         app_version = self.session.http.get(self.url, schema=validate.Schema(
54             validate.parse_html(),
55             validate.xml_xpath_string(".//head/meta[@name='appVersion']/@content"),
56             validate.any(None, str),
57         ))
58         if not app_version:
59             return
60 
61         log.debug(f"app_version={app_version}")
62 
63         return self.session.http.get(
64             "https://boot.pluto.tv/v4/start",
65             params={
66                 "appName": "web",
67                 "appVersion": app_version,
68                 "deviceVersion": "94.0.0",
69                 "deviceModel": "web",
70                 "deviceMake": "firefox",
71                 "deviceType": "web",
72                 "clientID": str(uuid4()),
73                 "clientModelNumber": "1.0",
74                 kind: slug,
75             },
76             schema=validate.Schema(
77                 validate.parse_json(), {
78                     "servers": {
79                         "stitcher": validate.url(),
80                     },
81                     validate.optional("EPG"): [{
82                         "name": str,
83                         "id": str,
84                         "slug": str,
85                         "stitched": {
86                             "path": str,
87                         },
88                     }],
89                     validate.optional("VOD"): [{
90                         "name": str,
91                         "id": str,
92                         "slug": str,
93                         "genre": str,
94                         "stitched": {
95                             "path": str,
96                         },
97                         validate.optional("seasons"): [{
98                             "episodes": validate.all(
99                                 [{
100                                     "name": str,
101                                     "_id": str,
102                                     "slug": str,
103                                     "stitched": {
104                                         "path": str,
105                                     },
106                                 }],
107                                 validate.filter(lambda k: slugfilter and k["slug"] == slugfilter),
108                             ),
109                         }],
110                     }],
111                     "sessionToken": str,
112                     "stitcherParams": str,
113                 },
114             ),
115         )
116 
117     def _get_playlist(self, host, path, params, token):
118         qs = parse_qs(params)
119         qs["jwt"] = token
120         yield from PlutoHLSStream.parse_variant_playlist(self.session, update_qsd(urljoin(host, path), qs)).items()
121 
122     @staticmethod
123     def _get_media_data(data, key, slug):
124         media = data.get(key)
125         if media and media[0]["slug"] == slug:
126             return media[0]
127 
128     def _get_streams(self):
129         m = self.match.groupdict()
130         if m["slug_live"]:
131             data = self._get_api_data("channelSlug", m["slug_live"])
132             media = self._get_media_data(data, "EPG", m["slug_live"])
133             if not media:
134                 return
135 
136             self.id = media["id"]
137             self.title = media["name"]
138             path = media["stitched"]["path"]
139 
140         elif m["slug_series"] and m["slug_episode"]:
141             data = self._get_api_data("episodeSlugs", m["slug_series"], slugfilter=m["slug_episode"])
142             media = self._get_media_data(data, "VOD", m["slug_series"])
143             if not media or "seasons" not in media:
144                 return
145 
146             for season in media["seasons"]:
147                 if season["episodes"]:
148                     episode = season["episodes"][0]
149                     if episode["slug"] == m["slug_episode"]:
150                         break
151             else:
152                 return
153 
154             self.author = media["name"]
155             self.category = media["genre"]
156             self.id = episode["_id"]
157             self.title = episode["name"]
158             path = episode["stitched"]["path"]
159 
160         elif m["slug_movies"]:
161             data = self._get_api_data("episodeSlugs", m["slug_movies"])
162             media = self._get_media_data(data, "VOD", m["slug_movies"])
163             if not media:
164                 return
165 
166             self.category = media["genre"]
167             self.id = media["id"]
168             self.title = media["name"]
169             path = media["stitched"]["path"]
170 
171         else:
172             return
173 
174         log.trace(f"data={data!r}")
175         log.debug(f"path={path}")
176 
177         return self._get_playlist(
178             data["servers"]["stitcher"],
179             path,
180             data["stitcherParams"],
181             data["sessionToken"],
182         )
183 
184 
185 __plugin__ = Pluto
186
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/streamlink/plugins/pluto.py b/src/streamlink/plugins/pluto.py
--- a/src/streamlink/plugins/pluto.py
+++ b/src/streamlink/plugins/pluto.py
@@ -10,7 +10,7 @@
 
 import logging
 import re
-from urllib.parse import parse_qs, urljoin
+from urllib.parse import parse_qsl, urljoin
 from uuid import uuid4
 
 from streamlink.plugin import Plugin, pluginmatcher
@@ -23,7 +23,7 @@
 
 
 class PlutoHLSStreamWriter(HLSStreamWriter):
-    ad_re = re.compile(r"_ad/creative/|dai\.google\.com|Pluto_TV_OandO/.*Bumper")
+    ad_re = re.compile(r"_ad/creative/|dai\.google\.com|Pluto_TV_OandO/.*(Bumper|plutotv_filler)")
 
     def should_filter_sequence(self, sequence):
         return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)
@@ -115,9 +115,13 @@
         )
 
     def _get_playlist(self, host, path, params, token):
-        qs = parse_qs(params)
-        qs["jwt"] = token
-        yield from PlutoHLSStream.parse_variant_playlist(self.session, update_qsd(urljoin(host, path), qs)).items()
+        qsd = dict(parse_qsl(params))
+        qsd["jwt"] = token
+
+        url = urljoin(host, path)
+        url = update_qsd(url, qsd)
+
+        return PlutoHLSStream.parse_variant_playlist(self.session, url)
 
     @staticmethod
     def _get_media_data(data, key, slug):

{"golden_diff": "diff --git a/src/streamlink/plugins/pluto.py b/src/streamlink/plugins/pluto.py\n--- a/src/streamlink/plugins/pluto.py\n+++ b/src/streamlink/plugins/pluto.py\n@@ -10,7 +10,7 @@\n \n import logging\n import re\n-from urllib.parse import parse_qs, urljoin\n+from urllib.parse import parse_qsl, urljoin\n from uuid import uuid4\n \n from streamlink.plugin import Plugin, pluginmatcher\n@@ -23,7 +23,7 @@\n \n \n class PlutoHLSStreamWriter(HLSStreamWriter):\n-    ad_re = re.compile(r\"_ad/creative/|dai\\.google\\.com|Pluto_TV_OandO/.*Bumper\")\n+    ad_re = re.compile(r\"_ad/creative/|dai\\.google\\.com|Pluto_TV_OandO/.*(Bumper|plutotv_filler)\")\n \n     def should_filter_sequence(self, sequence):\n         return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)\n@@ -115,9 +115,13 @@\n         )\n \n     def _get_playlist(self, host, path, params, token):\n-        qs = parse_qs(params)\n-        qs[\"jwt\"] = token\n-        yield from PlutoHLSStream.parse_variant_playlist(self.session, update_qsd(urljoin(host, path), qs)).items()\n+        qsd = dict(parse_qsl(params))\n+        qsd[\"jwt\"] = token\n+\n+        url = urljoin(host, path)\n+        url = update_qsd(url, qsd)\n+\n+        return PlutoHLSStream.parse_variant_playlist(self.session, url)\n \n     @staticmethod\n     def _get_media_data(data, key, slug):\n", "issue": "plugins.Pluto: Freezes at commercials and plays no audio on TV Shows\n### Checklist\n\n- [X] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)\n- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)\n- [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)\n- [x] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)\n\n### Streamlink version\n\nLatest build from the master branch\n\n### Description\n\nHello! Pluto TV plugin have problems. The plugin plays fine, but it freezes at commercials and some TV shows don't have commercials, it plays no audio because it thinks it at a commercial. This happens on all Pluto TV channels.\n\n### Debug log\n\n```text\nC:\\Windows\\system32>streamlink -l debug https://pluto.tv/live-tv/forever-kids best\r\n[cli][debug] OS:         Windows 10\r\n[cli][debug] Python:     3.11.5\r\n[cli][debug] Streamlink: 6.1.0\r\n[cli][debug] Dependencies:\r\n[cli][debug]  certifi: 2023.7.22\r\n[cli][debug]  isodate: 0.6.1\r\n[cli][debug]  lxml: 4.9.3\r\n[cli][debug]  pycountry: 22.3.5\r\n[cli][debug]  pycryptodome: 3.18.0\r\n[cli][debug]  PySocks: 1.7.1\r\n[cli][debug]  requests: 2.31.0\r\n[cli][debug]  trio: 0.22.2\r\n[cli][debug]  trio-websocket: 0.10.3\r\n[cli][debug]  typing-extensions: 4.7.1\r\n[cli][debug]  urllib3: 2.0.4\r\n[cli][debug]  websocket-client: 1.6.1\r\n[cli][debug] Arguments:\r\n[cli][debug]  url=https://pluto.tv/live-tv/forever-kids\r\n[cli][debug]  stream=['best']\r\n[cli][debug]  --loglevel=debug\r\n[cli][debug]  --ffmpeg-ffmpeg=C:\\Program Files (x86)\\Streamlink\\ffmpeg\\ffmpeg.exe\r\n[cli][info] Found matching plugin pluto for URL https://pluto.tv/live-tv/forever-kids\r\n[plugins.pluto][debug] slug=forever-kids\r\n[plugins.pluto][debug] app_version=7.7.0-18f7ab32608969ea5bcbce8d0e23b9d0e1b24717\r\n[stream.hls][warning] Encountered a stream discontinuity. This is unsupported and will result in incoherent output data.\n```\n\n", "before_files": [{"content": "\"\"\"\n$description Live TV and video on-demand service owned by Paramount Streaming.\n$url pluto.tv\n$type live, vod\n$metadata id\n$metadata author\n$metadata category\n$metadata title\n\"\"\"\n\nimport logging\nimport re\nfrom urllib.parse import parse_qs, urljoin\nfrom uuid import uuid4\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter\nfrom streamlink.utils.url import update_qsd\n\n\nlog = logging.getLogger(__name__)\n\n\nclass PlutoHLSStreamWriter(HLSStreamWriter):\n    ad_re = re.compile(r\"_ad/creative/|dai\\.google\\.com|Pluto_TV_OandO/.*Bumper\")\n\n    def should_filter_sequence(self, sequence):\n        return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)\n\n\nclass PlutoHLSStreamReader(HLSStreamReader):\n    __writer__ = PlutoHLSStreamWriter\n\n\nclass PlutoHLSStream(HLSStream):\n    __shortname__ = \"hls-pluto\"\n    __reader__ = PlutoHLSStreamReader\n\n\n@pluginmatcher(re.compile(r\"\"\"\n    https?://(?:www\\.)?pluto\\.tv/(?:\\w{2}/)?(?:\n        live-tv/(?P<slug_live>[^/]+)\n        |\n        on-demand/series/(?P<slug_series>[^/]+)(?:/season/\\d+)?/episode/(?P<slug_episode>[^/]+)\n        |\n        on-demand/movies/(?P<slug_movies>[^/]+)\n    )/?$\n\"\"\", re.VERBOSE))\nclass Pluto(Plugin):\n    def _get_api_data(self, kind, slug, slugfilter=None):\n        log.debug(f\"slug={slug}\")\n        app_version = self.session.http.get(self.url, schema=validate.Schema(\n            validate.parse_html(),\n            validate.xml_xpath_string(\".//head/meta[@name='appVersion']/@content\"),\n            validate.any(None, str),\n        ))\n        if not app_version:\n            return\n\n        log.debug(f\"app_version={app_version}\")\n\n        return self.session.http.get(\n            \"https://boot.pluto.tv/v4/start\",\n            params={\n                \"appName\": \"web\",\n                \"appVersion\": app_version,\n                \"deviceVersion\": \"94.0.0\",\n                \"deviceModel\": \"web\",\n                \"deviceMake\": \"firefox\",\n                \"deviceType\": \"web\",\n                \"clientID\": str(uuid4()),\n                \"clientModelNumber\": \"1.0\",\n                kind: slug,\n            },\n            schema=validate.Schema(\n                validate.parse_json(), {\n                    \"servers\": {\n                        \"stitcher\": validate.url(),\n                    },\n                    validate.optional(\"EPG\"): [{\n                        \"name\": str,\n                        \"id\": str,\n                        \"slug\": str,\n                        \"stitched\": {\n                            \"path\": str,\n                        },\n                    }],\n                    validate.optional(\"VOD\"): [{\n                        \"name\": str,\n                        \"id\": str,\n                        \"slug\": str,\n                        \"genre\": str,\n                        \"stitched\": {\n                            \"path\": str,\n                        },\n                        validate.optional(\"seasons\"): [{\n                            \"episodes\": validate.all(\n                                [{\n                                    \"name\": str,\n                                    \"_id\": str,\n                                    \"slug\": str,\n                                    \"stitched\": {\n                                        \"path\": str,\n                                    },\n                                }],\n                                validate.filter(lambda k: slugfilter and k[\"slug\"] == slugfilter),\n                            ),\n                        }],\n                    }],\n                    \"sessionToken\": str,\n                    \"stitcherParams\": str,\n                },\n            ),\n        )\n\n    def _get_playlist(self, host, path, params, token):\n        qs = parse_qs(params)\n        qs[\"jwt\"] = token\n        yield from PlutoHLSStream.parse_variant_playlist(self.session, update_qsd(urljoin(host, path), qs)).items()\n\n    @staticmethod\n    def _get_media_data(data, key, slug):\n        media = data.get(key)\n        if media and media[0][\"slug\"] == slug:\n            return media[0]\n\n    def _get_streams(self):\n        m = self.match.groupdict()\n        if m[\"slug_live\"]:\n            data = self._get_api_data(\"channelSlug\", m[\"slug_live\"])\n            media = self._get_media_data(data, \"EPG\", m[\"slug_live\"])\n            if not media:\n                return\n\n            self.id = media[\"id\"]\n            self.title = media[\"name\"]\n            path = media[\"stitched\"][\"path\"]\n\n        elif m[\"slug_series\"] and m[\"slug_episode\"]:\n            data = self._get_api_data(\"episodeSlugs\", m[\"slug_series\"], slugfilter=m[\"slug_episode\"])\n            media = self._get_media_data(data, \"VOD\", m[\"slug_series\"])\n            if not media or \"seasons\" not in media:\n                return\n\n            for season in media[\"seasons\"]:\n                if season[\"episodes\"]:\n                    episode = season[\"episodes\"][0]\n                    if episode[\"slug\"] == m[\"slug_episode\"]:\n                        break\n            else:\n                return\n\n            self.author = media[\"name\"]\n            self.category = media[\"genre\"]\n            self.id = episode[\"_id\"]\n            self.title = episode[\"name\"]\n            path = episode[\"stitched\"][\"path\"]\n\n        elif m[\"slug_movies\"]:\n            data = self._get_api_data(\"episodeSlugs\", m[\"slug_movies\"])\n            media = self._get_media_data(data, \"VOD\", m[\"slug_movies\"])\n            if not media:\n                return\n\n            self.category = media[\"genre\"]\n            self.id = media[\"id\"]\n            self.title = media[\"name\"]\n            path = media[\"stitched\"][\"path\"]\n\n        else:\n            return\n\n        log.trace(f\"data={data!r}\")\n        log.debug(f\"path={path}\")\n\n        return self._get_playlist(\n            data[\"servers\"][\"stitcher\"],\n            path,\n            data[\"stitcherParams\"],\n            data[\"sessionToken\"],\n        )\n\n\n__plugin__ = Pluto\n", "path": "src/streamlink/plugins/pluto.py"}], "after_files": [{"content": "\"\"\"\n$description Live TV and video on-demand service owned by Paramount Streaming.\n$url pluto.tv\n$type live, vod\n$metadata id\n$metadata author\n$metadata category\n$metadata title\n\"\"\"\n\nimport logging\nimport re\nfrom urllib.parse import parse_qsl, urljoin\nfrom uuid import uuid4\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter\nfrom streamlink.utils.url import update_qsd\n\n\nlog = logging.getLogger(__name__)\n\n\nclass PlutoHLSStreamWriter(HLSStreamWriter):\n    ad_re = re.compile(r\"_ad/creative/|dai\\.google\\.com|Pluto_TV_OandO/.*(Bumper|plutotv_filler)\")\n\n    def should_filter_sequence(self, sequence):\n        return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)\n\n\nclass PlutoHLSStreamReader(HLSStreamReader):\n    __writer__ = PlutoHLSStreamWriter\n\n\nclass PlutoHLSStream(HLSStream):\n    __shortname__ = \"hls-pluto\"\n    __reader__ = PlutoHLSStreamReader\n\n\n@pluginmatcher(re.compile(r\"\"\"\n    https?://(?:www\\.)?pluto\\.tv/(?:\\w{2}/)?(?:\n        live-tv/(?P<slug_live>[^/]+)\n        |\n        on-demand/series/(?P<slug_series>[^/]+)(?:/season/\\d+)?/episode/(?P<slug_episode>[^/]+)\n        |\n        on-demand/movies/(?P<slug_movies>[^/]+)\n    )/?$\n\"\"\", re.VERBOSE))\nclass Pluto(Plugin):\n    def _get_api_data(self, kind, slug, slugfilter=None):\n        log.debug(f\"slug={slug}\")\n        app_version = self.session.http.get(self.url, schema=validate.Schema(\n            validate.parse_html(),\n            validate.xml_xpath_string(\".//head/meta[@name='appVersion']/@content\"),\n            validate.any(None, str),\n        ))\n        if not app_version:\n            return\n\n        log.debug(f\"app_version={app_version}\")\n\n        return self.session.http.get(\n            \"https://boot.pluto.tv/v4/start\",\n            params={\n                \"appName\": \"web\",\n                \"appVersion\": app_version,\n                \"deviceVersion\": \"94.0.0\",\n                \"deviceModel\": \"web\",\n                \"deviceMake\": \"firefox\",\n                \"deviceType\": \"web\",\n                \"clientID\": str(uuid4()),\n                \"clientModelNumber\": \"1.0\",\n                kind: slug,\n            },\n            schema=validate.Schema(\n                validate.parse_json(), {\n                    \"servers\": {\n                        \"stitcher\": validate.url(),\n                    },\n                    validate.optional(\"EPG\"): [{\n                        \"name\": str,\n                        \"id\": str,\n                        \"slug\": str,\n                        \"stitched\": {\n                            \"path\": str,\n                        },\n                    }],\n                    validate.optional(\"VOD\"): [{\n                        \"name\": str,\n                        \"id\": str,\n                        \"slug\": str,\n                        \"genre\": str,\n                        \"stitched\": {\n                            \"path\": str,\n                        },\n                        validate.optional(\"seasons\"): [{\n                            \"episodes\": validate.all(\n                                [{\n                                    \"name\": str,\n                                    \"_id\": str,\n                                    \"slug\": str,\n                                    \"stitched\": {\n                                        \"path\": str,\n                                    },\n                                }],\n                                validate.filter(lambda k: slugfilter and k[\"slug\"] == slugfilter),\n                            ),\n                        }],\n                    }],\n                    \"sessionToken\": str,\n                    \"stitcherParams\": str,\n                },\n            ),\n        )\n\n    def _get_playlist(self, host, path, params, token):\n        qsd = dict(parse_qsl(params))\n        qsd[\"jwt\"] = token\n\n        url = urljoin(host, path)\n        url = update_qsd(url, qsd)\n\n        return PlutoHLSStream.parse_variant_playlist(self.session, url)\n\n    @staticmethod\n    def _get_media_data(data, key, slug):\n        media = data.get(key)\n        if media and media[0][\"slug\"] == slug:\n            return media[0]\n\n    def _get_streams(self):\n        m = self.match.groupdict()\n        if m[\"slug_live\"]:\n            data = self._get_api_data(\"channelSlug\", m[\"slug_live\"])\n            media = self._get_media_data(data, \"EPG\", m[\"slug_live\"])\n            if not media:\n                return\n\n            self.id = media[\"id\"]\n            self.title = media[\"name\"]\n            path = media[\"stitched\"][\"path\"]\n\n        elif m[\"slug_series\"] and m[\"slug_episode\"]:\n            data = self._get_api_data(\"episodeSlugs\", m[\"slug_series\"], slugfilter=m[\"slug_episode\"])\n            media = self._get_media_data(data, \"VOD\", m[\"slug_series\"])\n            if not media or \"seasons\" not in media:\n                return\n\n            for season in media[\"seasons\"]:\n                if season[\"episodes\"]:\n                    episode = season[\"episodes\"][0]\n                    if episode[\"slug\"] == m[\"slug_episode\"]:\n                        break\n            else:\n                return\n\n            self.author = media[\"name\"]\n            self.category = media[\"genre\"]\n            self.id = episode[\"_id\"]\n            self.title = episode[\"name\"]\n            path = episode[\"stitched\"][\"path\"]\n\n        elif m[\"slug_movies\"]:\n            data = self._get_api_data(\"episodeSlugs\", m[\"slug_movies\"])\n            media = self._get_media_data(data, \"VOD\", m[\"slug_movies\"])\n            if not media:\n                return\n\n            self.category = media[\"genre\"]\n            self.id = media[\"id\"]\n            self.title = media[\"name\"]\n            path = media[\"stitched\"][\"path\"]\n\n        else:\n            return\n\n        log.trace(f\"data={data!r}\")\n        log.debug(f\"path={path}\")\n\n        return self._get_playlist(\n            data[\"servers\"][\"stitcher\"],\n            path,\n            data[\"stitcherParams\"],\n            data[\"sessionToken\"],\n        )\n\n\n__plugin__ = Pluto\n", "path": "src/streamlink/plugins/pluto.py"}]}