Repository: streamlink/streamlink

Issue:
plugins.picarto: Could not find server netloc
### 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

Plugin suddenly stopped working today. 
Checked on multiple streams as well as on Linux and Windows 10 with the same result.
I can still manually watch the streams on VLC with "https://1-edge1-eu-west.picarto.tv/stream/hls/golive%2bUSERNAME/index.m3u8" as URL source.

### Debug log

```text
C:\PICARTO>streamlink https://picarto.tv/USERNAME best -l debug
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.10.5
[cli][debug] Streamlink: 4.2.0
[cli][debug] Dependencies:
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.1
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.15.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.28.1
[cli][debug]  websocket-client: 1.3.3
[cli][debug] Arguments:
[cli][debug]  url=https://picarto.tv/USERNAME
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin picarto for URL https://picarto.tv/USERNAME
[plugins.picarto][debug] Type=Live
[plugins.picarto][error] Could not find server netloc
error: No playable streams found on this URL: https://picarto.tv/USERNAME
```



Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/picarto.py b/src/streamlink/plugins/picarto.py
--- a/src/streamlink/plugins/picarto.py
+++ b/src/streamlink/plugins/picarto.py
@@ -33,40 +33,37 @@ class Picarto(Plugin):
     HLS_URL = "https://{netloc}/stream/hls/{file_name}/index.m3u8"
 
     def get_live(self, username):
-        netloc = self.session.http.get(self.url, schema=validate.Schema(
-            validate.parse_html(),
-            validate.xml_xpath_string(".//script[contains(@src,'/stream/player.js')][1]/@src"),
-            validate.any(None, validate.transform(lambda src: urlparse(src).netloc))
-        ))
-        if not netloc:
-            log.error("Could not find server netloc")
-            return
-
-        channel, multistreams = self.session.http.get(self.API_URL_LIVE.format(username=username), schema=validate.Schema(
-            validate.parse_json(),
-            {
-                "channel": validate.any(None, {
-                    "stream_name": str,
-                    "title": str,
-                    "online": bool,
-                    "private": bool,
-                    "categories": [{"label": str}],
-                }),
-                "getMultiStreams": validate.any(None, {
-                    "multistream": bool,
-                    "streams": [{
-                        "name": str,
+        channel, multistreams, loadbalancer = self.session.http.get(
+            self.API_URL_LIVE.format(username=username),
+            schema=validate.Schema(
+                validate.parse_json(),
+                {
+                    "channel": validate.any(None, {
+                        "stream_name": str,
+                        "title": str,
                         "online": bool,
-                    }],
-                }),
-            },
-            validate.union_get("channel", "getMultiStreams")
-        ))
-        if not channel or not multistreams:
+                        "private": bool,
+                        "categories": [{"label": str}],
+                    }),
+                    "getMultiStreams": validate.any(None, {
+                        "multistream": bool,
+                        "streams": [{
+                            "name": str,
+                            "online": bool,
+                        }],
+                    }),
+                    "getLoadBalancerUrl": validate.any(None, {
+                        "url": validate.any(None, validate.transform(lambda url: urlparse(url).netloc))
+                    })
+                },
+                validate.union_get("channel", "getMultiStreams", "getLoadBalancerUrl"),
+            )
+        )
+        if not channel or not multistreams or not loadbalancer:
             log.debug("Missing channel or streaming data")
             return
 
-        log.trace(f"netloc={netloc!r}")
+        log.trace(f"loadbalancer={loadbalancer!r}")
         log.trace(f"channel={channel!r}")
         log.trace(f"multistreams={multistreams!r}")
 
@@ -83,7 +80,7 @@ def get_live(self, username):
         self.title = channel["title"]
 
         hls_url = self.HLS_URL.format(
-            netloc=netloc,
+            netloc=loadbalancer["url"],
             file_name=channel["stream_name"]
         )
 
@@ -110,7 +107,7 @@ def get_vod(self, vod_id):
             validate.parse_json(),
             {"data": {
                 "video": validate.any(None, {
-                    "id": str,
+                    "id": int,
                     "title": str,
                     "file_name": str,
                     "video_recording_image_url": str,


Test patch: