Vangelis66: Many thanks, but where exactly has https://github.com/streamlink/streamlink/issues/1059 gone ??? 
unlocKing: Looks like #1059 and #1067 have both been deleted
beardypig: Oops forgot to update the tests too... 
beardypig: Perhaps the user that created them has been blocked by github for posting URLs... 
diff --git a/src/streamlink/plugins/app17.py b/src/streamlink/plugins/app17.py
index d3f7b075..691d8a2a 100644
--- a/src/streamlink/plugins/app17.py
+++ b/src/streamlink/plugins/app17.py
@@ -68,23 +68,26 @@ class App17(Plugin):
             self.logger.info("Stream currently unavailable.")
             return
 
-        http_url = _rtmp_re.search(res.text).group(1)
-        yield "live", HTTPStream(self.session, http_url)
-
-        if 'pull-rtmp' in http_url:
-            url = http_url.replace("http:", "rtmp:").replace(".flv", "")
+        url = _rtmp_re.search(res.text).group(1)
+        if 'rtmp:' in url:
             stream = RTMPStream(self.session, {
                     "rtmp": url,
                     "live": True
                     })
             yield "live", stream
-
-        if 'wansu-global-pull-rtmp' in http_url:
-            url = http_url.replace(".flv", "/playlist.m3u8")
+        else:
+            yield "live", HTTPStream(self.session, url)
+
+        if '17app.co' in url:
+            prefix = url.replace("rtmp:", "http:").replace(".flv", "/playlist.m3u8")
+            if '/playlist.m3u8' not in prefix:
+                url = prefix + "/playlist.m3u8"
+            else:
+                url = prefix
             for stream in HLSStream.parse_variant_playlist(self.session, url).items():
                 yield stream
         else:
-            url = http_url.replace(".flv", ".m3u8")
+            url = url.replace(".flv", ".m3u8")
             yield "live", HLSStream(self.session, url)
 
 
diff --git a/src/streamlink/plugins/hitbox.py b/src/streamlink/plugins/hitbox.py
index ddb45036..4d7a3e9b 100644
--- a/src/streamlink/plugins/hitbox.py
+++ b/src/streamlink/plugins/hitbox.py
@@ -178,7 +178,7 @@ class Hitbox(Plugin):
         if not media_id:
             res = http.get(LIVE_API.format(channel))
             livestream = http.json(res, schema=_live_schema)
-            if livestream.get("media_hosted_media"):
+            if livestream["media_hosted_media"]:
                 hosted = _live_schema.validate(livestream["media_hosted_media"])
                 self.logger.info("{0} is hosting {1}", livestream["media_user_name"], hosted["media_user_name"])
                 livestream = hosted
diff --git a/src/streamlink/plugins/npo.py b/src/streamlink/plugins/npo.py
index 840af212..f04aaade 100644
--- a/src/streamlink/plugins/npo.py
+++ b/src/streamlink/plugins/npo.py
@@ -1,13 +1,8 @@
 """Plugin for NPO: Nederlandse Publieke Omroep
 
 Supports:
-   VODs:
-    - https://www.npo.nl/nos-journaal/07-07-2017/POW_03375651
-    - https://www.zapp.nl/topdoks/gemist/VPWON_1276930
-    - https://zappelin.nl/10-voor/gemist/VPWON_1271522
-   Live:
-    - https://www.npo.nl/live/npo-1
-    - https://zappelin.nl/tv-kijken
+   VODs: http://www.npo.nl/het-zandkasteel/POMS_S_NTR_059963
+   Live: http://www.npo.nl/live/nederland-1
 """
 
 import re
@@ -17,14 +12,12 @@ from streamlink.plugin.api import http
 from streamlink.plugin.api import useragents
 from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream
-from streamlink.stream import HTTPStream
 from streamlink.utils import parse_json
 
 
 class NPO(Plugin):
     api_url = "http://ida.omroep.nl/app.php/{endpoint}"
-    url_re = re.compile(r"https?://(\w+\.)?(npo\.nl|zapp\.nl|zappelin\.nl)/")
-    media_id_re = re.compile(r'''<npo-player\smedia-id=["'](?P<media_id>[^"']+)["']''')
+    url_re = re.compile(r"https?://(\w+\.)?(npo.nl|zapp.nl|zappelin.nl)/")
     prid_re = re.compile(r'''(?:data(-alt)?-)?prid\s*[=:]\s*(?P<q>["'])(\w+)(?P=q)''')
     react_re = re.compile(r'''data-react-props\s*=\s*(?P<q>["'])(?P<data>.*?)(?P=q)''')
 
@@ -85,11 +78,6 @@ class NPO(Plugin):
                 data = parse_json(m.group("data").replace("&quot;", '"'))
                 bprid = data.get("mid")
 
-        if bprid is None:
-            m = self.media_id_re.search(res.text)
-            if m:
-                bprid = m.group('media_id')
-
         return bprid
 
     def _get_streams(self):
@@ -103,7 +91,7 @@ class NPO(Plugin):
                                     schema=self.streams_schema)
 
             for stream in streams:
-                if stream["format"] in ("adaptive", "hls", "mp4"):
+                if stream["format"] in ("adaptive", "hls"):
                     if stream["contentType"] == "url":
                         stream_url = stream["url"]
                     else:
@@ -114,10 +102,8 @@ class NPO(Plugin):
                         stream_url = http.json(http.get(info_url),
                                                schema=self.stream_info_schema)
 
-                    if stream["format"] in ("adaptive", "hls"):
-                        for s in HLSStream.parse_variant_playlist(self.session, stream_url).items():
-                            yield s
-                    elif stream["format"] in ("mp3", "mp4"):
-                        yield "vod", HTTPStream(self.session, stream_url)
+                    for s in HLSStream.parse_variant_playlist(self.session, stream_url).items():
+                        yield s
+
 
 __plugin__ = NPO
diff --git a/src/streamlink/plugins/tvplayer.py b/src/streamlink/plugins/tvplayer.py
index 249e85fa..f79474e6 100644
--- a/src/streamlink/plugins/tvplayer.py
+++ b/src/streamlink/plugins/tvplayer.py
@@ -15,7 +15,7 @@ class TVPlayer(Plugin):
     dummy_postcode = "SE1 9LT"  # location of ITV HQ in London
 
     url_re = re.compile(r"https?://(?:www.)?tvplayer.com/(:?watch/?|watch/(.+)?)")
-    stream_attrs_re = re.compile(r'data-(resource|token)\s*=\s*"(.*?)"', re.S)
+    stream_attrs_re = re.compile(r'data-(resource|token|channel-id)\s*=\s*"(.*?)"', re.S)
     login_token_re = re.compile(r'input.*?name="token".*?value="(\w+)"')
     stream_schema = validate.Schema({
         "tvplayer": validate.Schema({
@@ -58,20 +58,22 @@ class TVPlayer(Plugin):
         # there is a 302 redirect on a successful login
         return res2.status_code == 302
 
-    def _get_stream_data(self, resource, token, service=1):
+    def _get_stream_data(self, resource, channel_id, token, service=1):
         # Get the context info (validation token and platform)
         self.logger.debug("Getting stream information for resource={0}".format(resource))
         context_res = http.get(self.context_url, params={"resource": resource,
                                                          "gen": token})
         context_data = http.json(context_res, schema=self.context_schema)
+        self.logger.debug("Context data: {0}", str(context_data))
 
         # get the stream urls
         res = http.post(self.api_url, data=dict(
             service=service,
-            id=resource,
+            id=channel_id,
             validate=context_data["validate"],
             token=context_data.get("token"),
-            platform=context_data["platform"]["key"]))
+            platform=context_data["platform"]["key"]),
+                        raise_for_status=False)
 
         return http.json(res, schema=self.stream_schema)
 
@@ -91,7 +93,8 @@ class TVPlayer(Plugin):
                             data=dict(postcode=self.dummy_postcode),
                             params=dict(return_url=self.url))
 
-        stream_attrs = dict((k, v.strip('"')) for k, v in self.stream_attrs_re.findall(res.text))
+        stream_attrs = dict((k.replace("-", "_"), v.strip('"')) for k, v in self.stream_attrs_re.findall(res.text))
+        self.logger.debug("Got stream attrs: {0}", str(stream_attrs))
 
         if "resource" in stream_attrs and "token" in stream_attrs:
             stream_data = self._get_stream_data(**stream_attrs)

tvplayer plugin broken
https://tvplayer.com/watch/bbcone
Unable to open URL: http://api.tvplayer.com/api/v2/stream/live (400 Client Error: Bad Request for url: http://api.tvplayer.com/api/v2/stream/live)
diff --git a/tests/test_plugin_tvplayer.py b/tests/test_plugin_tvplayer.py
index 52f27dc0..f9f13367 100644
--- a/tests/test_plugin_tvplayer.py
+++ b/tests/test_plugin_tvplayer.py
@@ -41,7 +41,7 @@ class TestPluginTVPlayer(unittest.TestCase):
         page_resp = Mock()
         page_resp.text = u"""
                     <div class="video-js theoplayer-skin theo-seekbar-above-controls content-box vjs-fluid"
-                 data-resource= "89"
+                 data-resource= "bbcone"
                  data-token = "1324567894561268987948596154656418448489159"
                                     data-content-type="live"
                     data-environment="live"
@@ -54,6 +54,7 @@ class TestPluginTVPlayer(unittest.TestCase):
         mock_http.get.return_value = page_resp
         hlsstream.parse_variant_playlist.return_value = {"test": HLSStream(self.session, "http://test.se/stream1")}
 
+        TVPlayer.bind(self.session, "test.plugin.tvplayer")
         plugin = TVPlayer("http://tvplayer.com/watch/dave")
 
         streams = plugin.get_streams()
@@ -63,7 +64,7 @@ class TestPluginTVPlayer(unittest.TestCase):
         # test the url is used correctly
         mock_http.get.assert_called_with("http://tvplayer.com/watch/dave")
         # test that the correct API call is made
-        mock_get_stream_data.assert_called_with(resource="89", token="1324567894561268987948596154656418448489159")
+        mock_get_stream_data.assert_called_with(resource="bbcone", channel_id="89", token="1324567894561268987948596154656418448489159")
         # test that the correct URL is used for the HLSStream
         hlsstream.parse_variant_playlist.assert_called_with(ANY, "http://test.se/stream1")
 
@@ -76,6 +77,7 @@ class TestPluginTVPlayer(unittest.TestCase):
         """
         mock_http.get.return_value = page_resp
 
+        TVPlayer.bind(self.session, "test.plugin.tvplayer")
         plugin = TVPlayer("http://tvplayer.com/watch/dave")
 
         streams = plugin.get_streams()

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov codecov coverage mock pynsist unittest2
apt-get update apt-get install -y gcc
3.6
dev-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
codecov==2.1.13
coverage==6.2
distlib==0.3.9
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
iso-639==0.4.5
iso3166==2.1.1
Jinja2==3.0.3
linecache2==1.0.0
MarkupSafe==2.0.1
mock==5.2.0
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycryptodome==3.21.0
pynsist==2.8
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
requests==2.27.1
requests_download==0.1.2
six==1.17.0
-e git+https://github.com/streamlink/streamlink.git@4761570f479ba51ffeb099a4e8a2ed3fea6df72d#egg=streamlink
tomli==1.2.3
traceback2==1.4.0
typing_extensions==4.1.1
unittest2==1.1.0
urllib3==1.26.20
yarg==0.1.10
zipp==3.6.0

name: streamlink
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - argparse==1.4.0
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - coverage==6.2
      - distlib==0.3.9
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - iso-639==0.4.5
      - iso3166==2.1.1
      - jinja2==3.0.3
      - linecache2==1.0.0
      - markupsafe==2.0.1
      - mock==5.2.0
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycryptodome==3.21.0
      - pynsist==2.8
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - requests==2.27.1
      - requests-download==0.1.2
      - six==1.17.0
      - tomli==1.2.3
      - traceback2==1.4.0
      - typing-extensions==4.1.1
      - unittest2==1.1.0
      - urllib3==1.26.20
      - yarg==0.1.10
      - zipp==3.6.0
prefix: /opt/conda/envs/streamlink

tests/test_plugin_tvplayer.py::TestPluginTVPlayer::test_get_streams
tests/test_plugin_tvplayer.py::TestPluginTVPlayer::test_can_handle_url tests/test_plugin_tvplayer.py::TestPluginTVPlayer::test_get_invalid_page
src/streamlink/plugins/npo.py src/streamlink/plugins/app17.py src/streamlink/plugins/hitbox.py src/streamlink/plugins/tvplayer.py
src/streamlink/plugins/npo.py src/streamlink/plugins/app17.py src/streamlink/plugins/hitbox.py src/streamlink/plugins/tvplayer.py