Repository: streamlink/streamlink

Issue:
plugins.rtve: Live streams 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, live streams from RTVE 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.rtve.es/play/videos/directo/la-1/
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin rtve for URL https://www.rtve.es/play/videos/directo/la-1/
error: No playable streams found on this URL: https://www.rtve.es/play/videos/directo/la-1/
```



Discussion:
The plugin has just been rewritten recently in the 4.2.0 release (#4632) where it needed to parse stream URLs from a custom binary format in base64 encoded data disguised as a PNG image request. Now the site has completely changed again.

From what it looks like, stream URLs can be requested via `https://ztnr.rtve.es/ztnr/{id}.m3u8` (the ID extraction of the current plugin implementation is still working) which then redirects to the actual stream URL.

This yields the following static stream URLs for live streams (geo-blocked segment requests, even with Spanish proxy/VPN):
- https://rtvelivestream.akamaized.net/segments/la1/la1_main.m3u8
- https://rtvelivestreamv6.akamaized.net/rtvesec/la2/la2_bkp.m3u8
- https://rtvelivestreamv6.akamaized.net/segments/24h/24h_main.m3u8
- https://rtvelivestreamv6.akamaized.net/segments/tdp/tdp_main.m3u8
- https://rtvelivestreamv6.akamaized.net/segments/clan/clan_main.m3u8

However, VOD content except for TV shows (I think) are still working fine with the current plugin implementation, eg:
- https://www.rtve.es/play/videos/informe-semanal/la-semilla-de-la-guerra/6670279/

Resolving patch:
diff --git a/src/streamlink/plugins/rtve.py b/src/streamlink/plugins/rtve.py
--- a/src/streamlink/plugins/rtve.py
+++ b/src/streamlink/plugins/rtve.py
@@ -127,6 +127,7 @@ def translate(cls, data: str) -> Iterator[Tuple[str, str]]:
     is_global=True,
 )
 class Rtve(Plugin):
+    URL_M3U8 = "https://ztnr.rtve.es/ztnr/{id}.m3u8"
     URL_VIDEOS = "https://ztnr.rtve.es/ztnr/movil/thumbnail/rtveplayw/videos/{id}.png?q=v2"
     URL_SUBTITLES = "https://www.rtve.es/api/videos/{id}/subtitulos.json"
 
@@ -145,6 +146,8 @@ def _get_streams(self):
         if not self.id:
             return
 
+        # check obfuscated stream URLs via self.URL_VIDEOS and ZTNR.translate() first
+        # self.URL_M3U8 appears to be valid for all streams, but doesn't provide any content in same cases
         urls = self.session.http.get(
             self.URL_VIDEOS.format(id=self.id),
             schema=validate.Schema(
@@ -154,12 +157,16 @@ def _get_streams(self):
             ),
         )
 
-        url = next((url for _, url in urls if urlparse(url).path.endswith(".m3u8")), None)
-        if not url:
-            url = next((url for _, url in urls if urlparse(url).path.endswith(".mp4")), None)
-            if url:
-                yield "vod", HTTPStream(self.session, url)
-            return
+        # then fall back to self.URL_M3U8
+        if not urls:
+            url = self.URL_M3U8.format(id=self.id)
+        else:
+            url = next((url for _, url in urls if urlparse(url).path.endswith(".m3u8")), None)
+            if not url:
+                url = next((url for _, url in urls if urlparse(url).path.endswith(".mp4")), None)
+                if url:
+                    yield "vod", HTTPStream(self.session, url)
+                return
 
         streams = HLSStream.parse_variant_playlist(self.session, url).items()
 
@@ -173,8 +180,8 @@ def _get_streams(self):
                             "items": [{
                                 "lang": str,
                                 "src": validate.url(),
-                            }]
-                        }
+                            }],
+                        },
                     },
                     validate.get(("page", "items")),
                 ),


Test patch: