Repository: streamlink/streamlink

Issue:
plugins.ard_mediathek: No playable streams found on this URL
### 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

The ARD (German Public TV) seems not to be working at the moment with Streamlink. 

It gives a 'error: No playable streams found on this URL: https://www.ardmediathek.de/daserste/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL0xpdmVzdHJlYW0tRGFzRXJzdGU'


### Debug log

```text
[cli][debug] OS:         Linux-5.10.0-15-amd64-x86_64-with-glibc2.31
[cli][debug] Python:     3.9.2
[cli][debug] Streamlink: 0.0.0+unknown
[cli][debug] Dependencies:
[cli][debug]  PySocks: 1.7.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.8.0
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.14.1
[cli][debug]  requests: 2.27.1
[cli][debug]  websocket-client: 1.3.1
[cli][debug]  importlib-metadata: 4.11.3
[cli][debug] Arguments:
[cli][debug]  url=https://www.ardmediathek.de/daserste/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL0xpdmVzdHJlYW0tRGFzRXJzdGU
[cli][debug]  stream=['720p']
[cli][debug]  --loglevel=debug
[cli][debug]  --output=blah.mp4
[cli][debug]  --http-proxy=socks5h://xxx.xxxd@xxxx.dynv6.net:8200
[cli][info] Found matching plugin ard_mediathek for URL https://www.ardmediathek.de/daserste/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL0xpdmVzdHJlYW0tRGFzRXJzdGU
error: No playable streams found on this URL: https://www.ardmediathek.de/daserste/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL0xpdmVzdHJlYW0tRGFzRXJzdGU
```



Discussion:
The response of the initial HTTP request seems to be empty now, so the plugin can't find the stream URL. Not sure yet why.

> ```html
> <script id="fetchedContextValue" type="application/json">
>     null
> </script>
> ```

The same request gets made by their site though and proper JSON data is included. That means that most likely some headers are missing.

----

A couple of live streams on `ard_mediathek` appear to be static now:
- DasErste: https://mcdn.daserste.de/daserste/de/master.m3u8
- WDR: https://mcdn.wdr.de/wdr/wdrfs/de/master.m3u8
- NDR: https://mcdn.ndr.de/ndr/hls/ndr_fs/ndr_nds/master.m3u8
- BR: https://mcdn.br.de/br/fs/bfs_sued/hls/de/master.m3u8
- ArdOne: https://mcdn.one.ard.de/ardone/hls/master.m3u8
- Alpha: https://mcdn.br.de/br/fs/ard_alpha/hls/de/master.m3u8

Others are hosed on Akamai and are probably not static:
- SWR: https://swrbwd-hls.akamaized.net/hls/live/2018672/swrbwd/master.m3u8
- HR: https://hrhlsde.akamaized.net/hls/live/2024526/hrhlsde/index.m3u8
- MDR: https://mdrtvsnhls.akamaized.net/hls/live/2016928/mdrtvsn/index.m3u8
- RadioBremen: https://rbhlslive.akamaized.net/hls/live/2020435/rbfs/master.m3u8
- RBB: https://rbb-hls-brandenburg.akamaized.net/hls/live/2017825/rbb_brandenburg/index.m3u8
- SR: https://srfs.akamaized.net/hls/live/689649/srfsgeo/index.m3u8
- Arte: https://arteliveext.akamaized.net/hls/live/2030993/artelive_de/index.m3u8
- KiKa: https://kikageohls.akamaized.net/hls/live/2022693-b/livetvkika_de/master.m3u8
- 3Sat: https://zdf-hls-18.akamaized.net/hls/live/2016501/dach/high/master.m3u8
- TagesSchau24: https://tagesschau.akamaized.net/hls/live/2020115/tagesschau/tagesschau_1/master.m3u8
- Phoenix: https://zdf-hls-19.akamaized.net/hls/live/2016502/de/high/master.m3u8
- DeutscheWelle: https://dwamdstream107.akamaized.net/hls/live/2017968/dwstream107/index.m3u8
Had a second look at the plugin and managed to fix the JSON data retrieval and validation, so that the correct stream URLs get found. That would be the diff (not submitting a PR yet):
```diff
diff --git a/src/streamlink/plugins/ard_mediathek.py b/src/streamlink/plugins/ard_mediathek.py
index c2c51c14..5cc1cdfd 100644
--- a/src/streamlink/plugins/ard_mediathek.py
+++ b/src/streamlink/plugins/ard_mediathek.py
@@ -33,14 +33,20 @@ class ARDMediathek(Plugin):
     def _get_streams(self):
         data_json = self.session.http.get(self.url, schema=validate.Schema(
             validate.parse_html(),
-            validate.xml_findtext(".//script[@id='fetchedContextValue'][@type='application/json']"),
+            validate.xml_findtext(".//script[@id='fetchedContextValue2'][@type='application/json']"),
             validate.any(None, validate.all(
                 validate.parse_json(),
-                {str: dict},
-                validate.transform(lambda obj: list(obj.items())),
-                validate.filter(lambda item: item[0].startswith("https://api.ardmediathek.de/page-gateway/pages/")),
-                validate.any(validate.get((0, 1)), [])
-            ))
+                [list],
+                validate.filter(lambda item: (
+                    len(item) == 2
+                    and type(item[0]) is str
+                    and type(item[1]) is dict
+                    and item[0].startswith("https://api.ardmediathek.de/page-gateway/pages/")
+                )),
+                validate.get((0, 1)),
+                {"data": dict},
+                validate.get("data"),
+            )),
         ))
         if not data_json:
             return
```

However, all the static streams I've listed earlier have a separate HLS audio playlist, and instead of using MPEG-TS containers for the audio segments, ADTS containers are used for the AAC audio data, and somehow this causes problems when muxing the two streams into the output.

From what it looks like, the audio stream alone is causing issues when trying to play it on the fly. Saving it and playing it back afterwards works though. @back-to, do you have any idea?

Video: https://mcdn.daserste.de/daserste/de/master_1920p_5000.m3u8
Audio: https://mcdn.daserste.de/daserste/de/master_audio1_128.m3u8
https://datatracker.ietf.org/doc/html/rfc8216#section-3.4

https://github.com/streamlink/streamlink/issues/3534#issuecomment-778171025

Resolving patch:
diff --git a/src/streamlink/plugins/ard_mediathek.py b/src/streamlink/plugins/ard_mediathek.py
--- a/src/streamlink/plugins/ard_mediathek.py
+++ b/src/streamlink/plugins/ard_mediathek.py
@@ -19,29 +19,50 @@
 
 
 @pluginmatcher(re.compile(
-    r"https?://(?:(\w+\.)?ardmediathek\.de/|mediathek\.daserste\.de/)"
+    r"""
+        https?://(\w+\.)?ardmediathek\.de/
+        (?:
+            live/(?:[^/]+/)?(?P<id_live>\w+)
+            |
+            video/(?:[^/]+/[^/]+/[^/]+/)?(?P<id_video>\w+)
+        )
+        (?:\?|$)
+    """,
+    re.VERBOSE,
 ))
 class ARDMediathek(Plugin):
+    _URL_API = "https://api.ardmediathek.de/page-gateway/pages/ard/item/{item}"
     _QUALITY_MAP = {
         4: "1080p",
         3: "720p",
         2: "540p",
         1: "360p",
-        0: "270p"
+        0: "270p",
     }
 
     def _get_streams(self):
         data_json = self.session.http.get(self.url, schema=validate.Schema(
             validate.parse_html(),
-            validate.xml_findtext(".//script[@id='fetchedContextValue'][@type='application/json']"),
-            validate.any(None, validate.all(
+            validate.xml_xpath_string(".//script[@type='application/json'][@id='fetchedContextValue2'][1]/text()"),
+            validate.none_or_all(
                 validate.parse_json(),
-                {str: dict},
-                validate.transform(lambda obj: list(obj.items())),
+                [validate.list(str, {"data": dict})],
                 validate.filter(lambda item: item[0].startswith("https://api.ardmediathek.de/page-gateway/pages/")),
-                validate.any(validate.get((0, 1)), [])
-            ))
+                validate.any(
+                    validate.get((0, 1, "data")),
+                    [],
+                ),
+            ),
         ))
+        if not data_json:
+            data_json = self.session.http.get(
+                self._URL_API.format(item=self.match.group("id_live") or self.match.group("id_video")),
+                params={
+                    "devicetype": "pc",
+                    "embedded": "false",
+                },
+                schema=validate.Schema(validate.parse_json()),
+            )
         if not data_json:
             return
 


Test patch:
diff --git a/tests/plugins/test_ard_mediathek.py b/tests/plugins/test_ard_mediathek.py
--- a/tests/plugins/test_ard_mediathek.py
+++ b/tests/plugins/test_ard_mediathek.py
@@ -5,12 +5,35 @@
 class TestPluginCanHandleUrlARDMediathek(PluginCanHandleUrl):
     __plugin__ = ARDMediathek
 
-    should_match = [
-        'http://mediathek.daserste.de/live',
-        'http://www.ardmediathek.de/tv/Sportschau/'
-    ]
-
-    should_not_match = [
-        'https://daserste.de/live/index.html',
-        'https://www.daserste.de/live/index.html',
+    should_match_groups = [
+        (
+            "https://www.ardmediathek.de/live/"
+            + "Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ",
+            {"id_live": "Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ"},
+        ),
+        (
+            "https://www.ardmediathek.de/live/"
+            + "Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ?toolbarType=default",
+            {"id_live": "Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ"},
+        ),
+        (
+            "https://www.ardmediathek.de/live/tagesschau24/"
+            + "Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvbGl2ZXN0cmVhbQ",
+            {"id_live": "Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvbGl2ZXN0cmVhbQ"},
+        ),
+        (
+            "https://www.ardmediathek.de/video/"
+            + "Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvOWE4NGIzODgtZDEzNS00ZWU0LWI4ODEtZDYyNTQzYjg3ZmJlLzE",
+            {"id_video": "Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvOWE4NGIzODgtZDEzNS00ZWU0LWI4ODEtZDYyNTQzYjg3ZmJlLzE"},
+        ),
+        (
+            "https://www.ardmediathek.de/video/arte/blackfish-der-killerwal/arte/"
+            + "Y3JpZDovL2FydGUudHYvdmlkZW9zLzA1MDMyNy0wMDAtQQ",
+            {"id_video": "Y3JpZDovL2FydGUudHYvdmlkZW9zLzA1MDMyNy0wMDAtQQ"},
+        ),
+        (
+            "https://www.ardmediathek.de/video/expeditionen-ins-tierreich/die-revolte-der-schimpansen/ndr/"
+            + "Y3JpZDovL25kci5kZS9jY2E3M2MzZS00ZTljLTRhOWItODE3MC05MjhjM2MwNWEyMDM?toolbarType=default",
+            {"id_video": "Y3JpZDovL25kci5kZS9jY2E3M2MzZS00ZTljLTRhOWItODE3MC05MjhjM2MwNWEyMDM"},
+        ),
     ]