Repository: streamlink/streamlink

Issue:
plugins.atresplayer: Live streams is not working.
### 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

As for today, Atresplayer live streams is not working.

### Debug log

```text
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.10.6
[cli][debug] Streamlink: 4.3.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://www.atresplayer.com/directos/antena3/
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --hls-live-edge=1
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin atresplayer for URL https://www.atresplayer.com/directos/antena3/
error: Unable to validate response text: ValidationError(NoneOrAllSchema):
  ValidationError(type):
    Type of <generator object search_dict at 0x000002C64BA79930> should be list, but is generator
```



Discussion:
The validation schema is indeed broken. However, similar to the mitele plugin #4726, the site requires a paid subscription for all channels (I checked with a local German IP as well as a Spanish proxy/VPN).

Submitting a fix for the validation schemas is thus pointless if I can't check whether the plugin is working correctly.
@bastimeyer Neither atresplayer nor mitele requires a paid subscription as long as you have a spanish IP.
I just loaded those two pages with a spanish VPN and it works perfectly:
https://www.atresplayer.com/directos/antena3/ 
https://www.mitele.es/directo/telecinco/
The VPN I am using gets blocked and they are showing a requirement for a paid subscription, on their site as well as on the API.
```
$ curl -s 'https://api.atresplayer.com/player/v1/live/5a6a165a7ed1a834493ebf6a'
{"error":"required_paid","error_description":"Required paid"}

$ curl -s 'https://api.atresplayer.com/player/v1/live/5a6a165a7ed1a834493ebf6a'
{"error":"vpn","error_description":"Using VPN","detail":{"ip":"redacted","time":"2022-08-20T20:07:14Z"}}
```

I can open a PR with my changes for you to test though.

Resolving patch:
diff --git a/src/streamlink/plugins/atresplayer.py b/src/streamlink/plugins/atresplayer.py
--- a/src/streamlink/plugins/atresplayer.py
+++ b/src/streamlink/plugins/atresplayer.py
@@ -7,12 +7,12 @@
 
 import logging
 import re
+from urllib.parse import urlparse
 
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
 from streamlink.stream.dash import DASHStream
 from streamlink.stream.hls import HLSStream
-from streamlink.utils.data import search_dict
 from streamlink.utils.url import update_scheme
 
 log = logging.getLogger(__name__)
@@ -24,15 +24,15 @@
 class AtresPlayer(Plugin):
     def _get_streams(self):
         self.url = update_scheme("https://", self.url)
+        path = urlparse(self.url).path
 
         api_url = self.session.http.get(self.url, schema=validate.Schema(
             re.compile(r"""window.__PRELOADED_STATE__\s*=\s*({.*?});""", re.DOTALL),
             validate.none_or_all(
                 validate.get(1),
                 validate.parse_json(),
-                validate.transform(search_dict, key="href"),
-                [validate.url()],
-                validate.get(0),
+                {"links": {path: {"href": validate.url()}}},
+                validate.get(("links", path, "href")),
             ),
         ))
         if not api_url:
@@ -41,37 +41,46 @@ def _get_streams(self):
 
         player_api_url = self.session.http.get(api_url, schema=validate.Schema(
             validate.parse_json(),
-            validate.transform(search_dict, key="urlVideo"),
+            {"urlVideo": validate.url()},
+            validate.get("urlVideo"),
         ))
 
-        stream_schema = validate.Schema(
+        log.debug(f"Player API URL: {player_api_url}")
+        sources = self.session.http.get(player_api_url, acceptable_status=(200, 403), schema=validate.Schema(
             validate.parse_json(),
-            {
-                "sources": [
-                    validate.all(
-                        {
-                            "src": validate.url(),
-                            validate.optional("type"): str,
-                        },
-                    ),
-                ],
-            },
-            validate.get("sources"),
-        )
+            validate.any(
+                {
+                    "error": str,
+                    "error_description": str,
+                },
+                {
+                    "sources": [
+                        validate.all(
+                            {
+                                "src": validate.url(),
+                                validate.optional("type"): str,
+                            },
+                            validate.union_get("type", "src"),
+                        ),
+                    ],
+                },
+            ),
+        ))
+        if "error" in sources:
+            log.error(f"Player API error: {sources['error']} - {sources['error_description']}")
+            return
 
-        for api_url in player_api_url:
-            log.debug(f"Player API URL: {api_url}")
-            for source in self.session.http.get(api_url, schema=stream_schema):
-                log.debug(f"Stream source: {source['src']} ({source.get('type', 'n/a')})")
+        for streamtype, streamsrc in sources.get("sources"):
+            log.debug(f"Stream source: {streamsrc} ({streamtype or 'n/a'})")
 
-                if "type" not in source or source["type"] == "application/vnd.apple.mpegurl":
-                    streams = HLSStream.parse_variant_playlist(self.session, source["src"])
-                    if not streams:
-                        yield "live", HLSStream(self.session, source["src"])
-                    else:
-                        yield from streams.items()
-                elif source["type"] == "application/dash+xml":
-                    yield from DASHStream.parse_manifest(self.session, source["src"]).items()
+            if streamtype == "application/vnd.apple.mpegurl":
+                streams = HLSStream.parse_variant_playlist(self.session, streamsrc)
+                if not streams:
+                    yield "live", HLSStream(self.session, streamsrc)
+                else:
+                    yield from streams.items()
+            elif streamtype == "application/dash+xml":
+                yield from DASHStream.parse_manifest(self.session, streamsrc).items()
 
 
 __plugin__ = AtresPlayer


Test patch: