Repository: streamlink/streamlink

Issue:
artetv: de/fr Livestreams aren't playable anymore
### 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 build from the master branch

### Description

Since about a week the live channels aren't playable anymore. However VODs working fine.


### Debug log

```text
streamlink https://www.arte.tv/de/live/ worst -l debug
[cli][debug] OS:         Linux-5.14.3-arch1-1-x86_64-with-glibc2.33
[cli][debug] Python:     3.9.7
[cli][debug] Streamlink: 2.4.0+17.g24c59a2
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(0.59.0)
[cli][debug] Arguments:
[cli][debug]  url=https://www.arte.tv/de/live/
[cli][debug]  stream=['worst']
[cli][debug]  --loglevel=debug
[cli][info] Found matching plugin artetv for URL https://www.arte.tv/de/live/
error: No playable streams found on this URL: https://www.arte.tv/de/live/

streamlink https://www.arte.tv/fr/direct/ best -l debug
[cli][debug] OS:         Linux-5.14.3-arch1-1-x86_64-with-glibc2.33
[cli][debug] Python:     3.9.7
[cli][debug] Streamlink: 2.4.0+17.g24c59a2
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(0.59.0)
[cli][debug] Arguments:
[cli][debug]  url=https://www.arte.tv/fr/direct/
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][info] Found matching plugin artetv for URL https://www.arte.tv/fr/direct/
error: No playable streams found on this URL: https://www.arte.tv/fr/direct/
```

plugins.arte: switch to arte.tv v2 API
The Arte.tv V1 API doens't seem to work anymore for live streams (see #4026).

Both web site and mobile app use the V2 API, which requires an authentication token. The one from the website is used here for this fix.


Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/artetv.py b/src/streamlink/plugins/artetv.py
--- a/src/streamlink/plugins/artetv.py
+++ b/src/streamlink/plugins/artetv.py
@@ -1,5 +1,3 @@
-"""Plugin for Arte.tv, bi-lingual art and culture channel."""
-
 import logging
 import re
 from operator import itemgetter
@@ -9,25 +7,6 @@
 from streamlink.stream import HLSStream
 
 log = logging.getLogger(__name__)
-JSON_VOD_URL = "https://api.arte.tv/api/player/v1/config/{0}/{1}?platform=ARTE_NEXT"
-JSON_LIVE_URL = "https://api.arte.tv/api/player/v1/livestream/{0}"
-
-_video_schema = validate.Schema({
-    "videoJsonPlayer": {
-        "VSR": validate.any(
-            [],
-            {
-                validate.text: {
-                    "height": int,
-                    "mediaType": validate.text,
-                    "url": validate.text,
-                    "versionProg": int,
-                    "versionLibelle": validate.text
-                },
-            },
-        )
-    }
-})
 
 
 @pluginmatcher(re.compile(r"""
@@ -40,34 +19,49 @@
     )
 """, re.VERBOSE))
 class ArteTV(Plugin):
-    def _create_stream(self, streams):
-        variant, variantname = min([(stream["versionProg"], stream["versionLibelle"]) for stream in streams.values()],
-                                   key=itemgetter(0))
-        log.debug(f"Using the '{variantname}' stream variant")
-        for sname, stream in streams.items():
-            if stream["versionProg"] == variant:
-                if stream["mediaType"] == "hls":
-                    try:
-                        streams = HLSStream.parse_variant_playlist(self.session, stream["url"])
-                        yield from streams.items()
-                    except OSError as err:
-                        log.warning(f"Failed to extract HLS streams for {sname}/{stream['versionLibelle']}: {err}")
+    API_URL = "https://api.arte.tv/api/player/v2/config/{0}/{1}"
+    API_TOKEN = "MzYyZDYyYmM1Y2Q3ZWRlZWFjMmIyZjZjNTRiMGY4MzY4NzBhOWQ5YjE4MGQ1NGFiODJmOTFlZDQwN2FkOTZjMQ"
 
     def _get_streams(self):
-        language = self.match.group('language')
-        video_id = self.match.group('video_id')
-        if video_id is None:
-            json_url = JSON_LIVE_URL.format(language)
-        else:
-            json_url = JSON_VOD_URL.format(language, video_id)
-        res = self.session.http.get(json_url)
-        video = self.session.http.json(res, schema=_video_schema)
+        language = self.match.group("language")
+        video_id = self.match.group("video_id")
 
-        if not video["videoJsonPlayer"]["VSR"]:
+        json_url = self.API_URL.format(language, video_id or "LIVE")
+        headers = {
+            "Authorization": f"Bearer {self.API_TOKEN}"
+        }
+        streams, metadata = self.session.http.get(json_url, headers=headers, schema=validate.Schema(
+            validate.parse_json(),
+            {"data": {"attributes": {
+                "streams": validate.any(
+                    [],
+                    [
+                        validate.all(
+                            {
+                                "url": validate.url(),
+                                "slot": int,
+                                "protocol": validate.any("HLS", "HLS_NG"),
+                            },
+                            validate.union_get("slot", "protocol", "url")
+                        )
+                    ]
+                ),
+                "metadata": {
+                    "title": str,
+                    "subtitle": validate.any(None, str)
+                }
+            }}},
+            validate.get(("data", "attributes")),
+            validate.union_get("streams", "metadata")
+        ))
+
+        if not streams:
             return
 
-        vsr = video["videoJsonPlayer"]["VSR"]
-        return self._create_stream(vsr)
+        self.title = f"{metadata['title']} - {metadata['subtitle']}" if metadata["subtitle"] else metadata["title"]
+
+        for slot, protocol, url in sorted(streams, key=itemgetter(0)):
+            return HLSStream.parse_variant_playlist(self.session, url)
 
 
 __plugin__ = ArteTV


Test patch: