Repository: streamlink/streamlink

Issue:
plugins.nos: No plugin can handle URL | Unable to validate response text
### Checklist

- [x] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
- [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)

### Collaboration

- [x] [I will provide feedback should a pull request be opened with a fix for the plugin](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#pull-request-feedback)

### Streamlink version

7.1.2

### Description

None of the livestreams, collections or article videos on NOS.nl seem to be recognized (anymore).

Example video: https://nos.nl/l/2490404
Example collection: https://nos.nl/collectie/13950-natuurramp-libie
For current example livestreams, see https://nos.nl/live

### Debug log

```text
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.12.8
[cli][debug] OpenSSL:    OpenSSL 3.0.15 3 Sep 2024
[cli][debug] Streamlink: 7.1.2
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.28.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=https://nos.nl/l/2490404
[cli][debug]  --loglevel=debug
[cli][debug]  --default-stream=['best']
error: No plugin can handle URL: https://nos.nl/l/2490404

---

[session][debug] Loading plugin: nos
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.12.8
[cli][debug] OpenSSL:    OpenSSL 3.0.15 3 Sep 2024
[cli][debug] Streamlink: 7.1.2
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.28.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=https://nos.nl/live
[cli][debug]  --loglevel=debug
[cli][debug]  --default-stream=['best']
[cli][info] Found matching plugin nos for URL https://nos.nl/live
error: Unable to validate response text: ValidationError(NoneOrAllSchema):
  ValidationError(dict):
    Unable to validate value of key '@type'
    Context(equality):
      ['VideoObject', 'WebPage'] does not equal 'VideoObject'
```

Discussion hints:

Solution patch:
diff --git a/src/streamlink/plugins/nos.py b/src/streamlink/plugins/nos.py
index 12e7942d..46473831 100644
--- a/src/streamlink/plugins/nos.py
+++ b/src/streamlink/plugins/nos.py
@@ -19,7 +19,7 @@ log = logging.getLogger(__name__)
 
 
 @pluginmatcher(
-    re.compile(r"https?://(?:\w+\.)?nos\.nl/(?:live|video|collectie)"),
+    re.compile(r"https?://(?:\w+\.)?nos\.nl/(?:live$|livestream/|l/|video/|collectie/)"),
 )
 class NOS(Plugin):
     def _get_streams(self):
@@ -31,7 +31,13 @@ class NOS(Plugin):
                 validate.none_or_all(
                     validate.parse_json(),
                     {
-                        "@type": "VideoObject",
+                        "@type": validate.any(
+                            "VideoObject",
+                            validate.all(
+                                list,
+                                validate.contains("VideoObject"),
+                            ),
+                        ),
                         "encodingFormat": "application/vnd.apple.mpegurl",
                         "contentUrl": validate.url(),
                         "identifier": validate.any(int, str),


Test patch:
diff --git a/tests/plugins/test_nos.py b/tests/plugins/test_nos.py
index 5b1ceb44..57cdbe69 100644
--- a/tests/plugins/test_nos.py
+++ b/tests/plugins/test_nos.py
@@ -7,12 +7,17 @@ class TestPluginCanHandleUrlNOS(PluginCanHandleUrl):
 
     should_match = [
         "https://nos.nl/live",
-        "https://nos.nl/collectie/13781/livestream/2385081-ek-voetbal-engeland-schotland",
+        "https://nos.nl/livestream/2539164-schaatsen-wb-milwaukee-straks-de-massastart-m",
         "https://nos.nl/collectie/13951/video/2491092-dit-was-prinsjesdag",
+        "https://nos.nl/l/2490788",
         "https://nos.nl/video/2490788-meteoor-gespot-boven-noord-nederland",
     ]
 
     should_not_match = [
+        "https://nos.nl/livestream",
+        "https://nos.nl/l",
+        "https://nos.nl/video",
+        "https://nos.nl/collectie",
         "https://nos.nl/artikel/2385784-explosieve-situatie-leidde-tot-verwoeste-huizen-en-omgewaaide-bomen-leersum",
         "https://nos.nl/sport",
         "https://nos.nl/sport/videos",