Repository: streamlink/streamlink

Issue:
plugins.atresplayer: 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

It only happens with this link (nova), not with others.

### Debug log

```text
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.11.1
[cli][debug] Streamlink: 5.2.1
[cli][debug] Dependencies:
[cli][debug]  certifi: 2022.12.7
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.2
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.16.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.28.2
[cli][debug]  urllib3: 1.26.14
[cli][debug]  websocket-client: 1.4.2
[cli][debug] Arguments:
[cli][debug]  url=https://www.atresplayer.com/directos/nova/
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin atresplayer for URL https://www.atresplayer.com/directos/nova/
[plugins.atresplayer][debug] API URL: https://api.atresplayer.com/client/v1/page/live/5a6a180b7ed1a834493ebf6e
[plugins.atresplayer][debug] Player API URL: https://api.atresplayer.com/player/v1/live/5a6a180b7ed1a834493ebf6e
[plugins.atresplayer][debug] Stream source: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/master.m3u8 (application/vnd.apple.mpegurl)
[utils.l10n][debug] Language code: es_ES
[cli][info] Available streams: 360p (worst), 480p, 720p, 1080p (best)
[cli][info] Opening stream: 1080p (hls)
[cli][info] Starting player: "C:\Program Files\VideoLAN\VLC\vlc.exe"
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 4166611075; Last Sequence: 4166611078
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 4166611076; End Sequence: None
[stream.hls][debug] Adding segment 4166611076 to queue
[stream.hls][debug] Adding segment 4166611077 to queue
[stream.hls][debug] Adding segment 4166611078 to queue
[stream.hls][error] Failed to fetch segment 4166611076: Unable to open URL: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611076.ts (404 Client Error: Not Found for url: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611076.ts)
[stream.hls][error] Failed to fetch segment 4166611077: Unable to open URL: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611077.ts (404 Client Error: Not Found for url: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611077.ts)
[stream.hls][error] Failed to fetch segment 4166611078: Unable to open URL: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611078.ts (404 Client Error: Not Found for url: https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/2/br4/220629065114/92561/br4_166611078.ts)
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[cli][error] Try 1/1: Could not open stream <HLSStream ['hls', 'https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/bitrate_4.m3u8', 'https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/master.m3u8']> (Failed to read data from stream: Read timeout)
error: Could not open stream <HLSStream ['hls', 'https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/bitrate_4.m3u8', 'https://directo.atresmedia.com/d99c691686362f555c21feb4d596063ae356e2b4_1675105307/geonova/master.m3u8']>, tried 1 times, exiting
[cli][info] Closing currently open stream...
```


Discussion:
pablohoffman: Thanks for the feedback @bastimeyer, I've sent a new commit to address it.
bastimeyer: Could you please give the VODs a quick test?

That one VOD URL defined in the plugin test file has expired, but I'm not sure if your changes will work with any VODs.

```
$ ./script/test-plugin-urls.py atresplayer
:: Finding streams for URL: http://www.atresplayer.com/directos/antena3/
:::: Player API error: required_paid - Required paid
!! No streams found
:: Finding streams for URL: http://www.atresplayer.com/directos/lasexta/
:::: Player API error: required_paid - Required paid
!! No streams found
:: Finding streams for URL: https://www.atresplayer.com/directos/antena3/
:::: Player API error: required_paid - Required paid
!! No streams found
:: Finding streams for URL: https://www.atresplayer.com/flooxer/programas/unas/temporada-1/dario-eme-hache-sindy-takanashi-entrevista_123/
!! Error while fetching streams
```
pablohoffman: @bastimeyer Atresplayer became a streaming platform and doesn't host VODs anymore, only live streams and shows/movies (do those count as VODs?) although I'm not sure if any of them are free.

Atresmedia (the company behind Atresplayer) does have other websites (lasexta.com, antena3.com) that host VODs but those would require separate handling, and I don't have time to add support for those now.
bastimeyer: Then the pluginmatcher URL should get updated accordingly and the VOD removed from the test file. The plugin's metadata already states that it only supports live streams.
bastimeyer: I force-pushed onto your PR branch with the necessary changes squashed into a proper single commit.

Please [force-fetch](https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---force) locally and check if the channels listed here do indeed work (they are all geo-blocked and unlisted for me):
https://github.com/streamlink/streamlink/issues/5131#issuecomment-1622614570

Once validated, we can merge this.
Jeffade1: @bastimeyer Passing there I did a quick check for you.
- The usual important channels are working fine with the new plugin.
- The fast channels in the list you pointed are showing:

error: Unable to validate response text: ValidationError(GetItemSchema):
  Item 0 was not found in object []
bastimeyer: Thanks, @Jeffade1
I fixed the validation error, but since this new plugin version is limited to the data returned by
https://api.atresplayer.com/client/v1/info/channels
the other channels will require a different solution.
bastimeyer: I'm going to merge this now. If anyone wants to add support for the other channels, please open a new pull request. Thanks.

Patch:
diff --git a/src/streamlink/plugins/atresplayer.py b/src/streamlink/plugins/atresplayer.py
index 4db42a43..375cc6eb 100644
--- a/src/streamlink/plugins/atresplayer.py
+++ b/src/streamlink/plugins/atresplayer.py
@@ -7,7 +7,6 @@ $region Spain
 
 import logging
 import re
-from urllib.parse import urlparse
 
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
@@ -20,35 +19,33 @@ log = logging.getLogger(__name__)
 
 
 @pluginmatcher(re.compile(
-    r"https?://(?:www\.)?atresplayer\.com/",
+    r"https?://(?:www\.)?atresplayer\.com/directos/.+",
 ))
 class AtresPlayer(Plugin):
+    _channels_api_url = "https://api.atresplayer.com/client/v1/info/channels"
+    _player_api_url = "https://api.atresplayer.com/player/v1/live/{channel_id}"
+
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.url = update_scheme("https://", f"{self.url.rstrip('/')}/")
 
     def _get_streams(self):
-        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(),
-                {"links": {path: {"href": validate.url()}}},
-                validate.get(("links", path, "href")),
-            ),
-        ))
-        if not api_url:
-            return
-        log.debug(f"API URL: {api_url}")
-
-        player_api_url = self.session.http.get(api_url, schema=validate.Schema(
+        channel_path = f"/{self.url.split('/')[-2]}/"
+        channel_data = self.session.http.get(self._channels_api_url, schema=validate.Schema(
             validate.parse_json(),
-            {"urlVideo": validate.url()},
-            validate.get("urlVideo"),
+            [{
+                "id": str,
+                "link": {"url": str},
+            }],
+            validate.filter(lambda item: item["link"]["url"] == channel_path),
         ))
+        if not channel_data:
+            return
+        channel_id = channel_data[0]["id"]
 
+        player_api_url = self._player_api_url.format(channel_id=channel_id)
         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(),
             validate.any(


Test patch:
diff --git a/tests/plugins/test_atresplayer.py b/tests/plugins/test_atresplayer.py
index a7c87244..dc3df683 100644
--- a/tests/plugins/test_atresplayer.py
+++ b/tests/plugins/test_atresplayer.py
@@ -10,9 +10,12 @@ class TestPluginCanHandleUrlAtresPlayer(PluginCanHandleUrl):
     __plugin__ = AtresPlayer
 
     should_match = [
-        "http://www.atresplayer.com/directos/antena3/",
-        "http://www.atresplayer.com/directos/lasexta/",
         "https://www.atresplayer.com/directos/antena3/",
+        "https://www.atresplayer.com/directos/lasexta/",
+        "https://www.atresplayer.com/directos/antena3-internacional/",
+    ]
+
+    should_not_match = [
         "https://www.atresplayer.com/flooxer/programas/unas/temporada-1/dario-eme-hache-sindy-takanashi-entrevista_123/",
     ]