Repository: streamlink/streamlink

Issue:
ITV Player unable to open URL
- [x] This is a plugin issue and I have read the contribution guidelines.


### Description

ITV Player/Hub for the main ITV channel do not open and give an error message.


### Reproduction steps / Explicit stream URLs to test

Type "streamlink itv.com/hub/itv".

Other ITV Hub URL's such as hub/itv2 or hub/citv continue to work.

### Log output

[cli][info] Found matching plugin itvplayer for URL itv.com/hub/itv
error: Unable to open URL: http://cdn.dar.itv.com/private/aHR0cDovL2NzbS1lLnBsYXkuZGFyLml0di5jb20vY3NtL2V4dGxpdmUvMjQ3MTYyMTUubTN1OD95by5hYz10cnVlJnlvLmFkPWZhbHNlJnBtPWZyZWUmcmFuZG9tPTU2OTk0ODU2ODc0ODA1NzE0MjAmYXJlYT1pdHZwbGF5ZXIuc2ltdWxjYXN0JnNvdXJjZT1zaW11bGNhc3QmaGRldmlkPXgmc2l6ZT1wYXR0ZXJuJnN1cGVydGFnPWRmbHQsc2ltJnVzPWFubyZzaXRlPWl0diZvc3Zlcj0xMCZjaGFuYnJhbmQ9aXR2MSZvcz13aW5kb3dzJmhtb2Q9NjYmcHY9YnJvd3Nlci40LjEmcGxheWVyPWh0bWw1LmRlc2t0b3AmZG09bmxpJmhtYW49Y2hyb21lJnRkdXI9MjE2MDAmdHBhcnRzPTEmYXJwPXgmZGVmYXVsdD1EZWZhdWx0UGF0dGVybiZwbGlzdD1zaW1hZHJlcGxhY2VtZW50JnBsZmNpZD1zaW1hZHJlcGxhY2VtZW50/endpoint?hdnea=st=1563543051~exp=1563564651~acl=/*~data=nohubplus~hmac=0ebaf6d4f22f101378a6c7ed3903c1999f6b28efd29ee05763e5ec7bf0d27896&yo.channel=24716215 (404 Client Error:  for url: http://csm-e.play.dar.itv.com/csm/extlive/24716215.m3u8?yo.ac=true&yo.ad=false&pm=free&random=5699485687480571420&area=itvplayer.simulcast&source=simulcast&hdevid=x&size=pattern&supertag=dflt,sim&us=ano&site=itv&osver=10&chanbrand=itv1&os=windows&hmod=66&pv=browser.4.1&player=html5.desktop&dm=nli&hman=chrome&tdur=21600&tparts=1&arp=x&default=DefaultPattern&plist=simadreplacement&plfcid=simadreplacement)


### Additional comments, screenshots, etc.

I have noticed the site plays a ITV Hub promo when starting a livestream on the website itself before loading that stream. This does not occur for the other channels.


Discussion:
Confirmed. I get the same error, will take a look a bit later. 
if I made a comment here it was in my error. 
I have nothing to comment on this issue.
Is there anything to differentiate getting the ad roll m3u8 and the livestream? I'd like to fix this but all I can think of doing is excluding elements in the playlist array (line 75+ of the plugin). I'm replying to ask for more information about the problem than I've been able to ascertain by myself.
Seems like only the flash stream is supported for ITV1 at the moment... 
itv.com/hub/itv does point to the flash stream? it must be possible to skip over that segment of the playlist but i am unsure how to implement this.

Resolving patch:
diff --git a/src/streamlink/plugins/itvplayer.py b/src/streamlink/plugins/itvplayer.py
--- a/src/streamlink/plugins/itvplayer.py
+++ b/src/streamlink/plugins/itvplayer.py
@@ -6,13 +6,14 @@
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import useragents, validate
 from streamlink.plugin.api.utils import itertags
-from streamlink.stream import HLSStream
+from streamlink.stream import HLSStream, RTMPStream
 
 log = logging.getLogger(__name__)
 
 
 class ITVPlayer(Plugin):
     _url_re = re.compile(r"https?://(?:www.)?itv.com/hub/(?P<stream>.+)")
+    swf_url = "https://mediaplayer.itv.com/2.19.5%2Bbuild.a23aa62b1e/ITVMediaPlayer.swf"
     _video_info_schema = validate.Schema({
         "StatusCode": 200,
         "AdditionalInfo": {
@@ -35,8 +36,7 @@ class ITVPlayer(Plugin):
 
     @classmethod
     def can_handle_url(cls, url):
-        match = cls._url_re.match(url)
-        return match is not None
+        return cls._url_re.match(url) is not None
 
     @property
     def device_info(self):
@@ -54,18 +54,11 @@ def video_info(self):
             if div.attributes.get("id") == "video":
                 return div.attributes
 
-    def _get_streams(self):
-        """
-            Find all the streams for the ITV url
-            :return: Mapping of quality to stream
-        """
-        self.session.http.headers.update({"User-Agent": useragents.FIREFOX})
+    def _get_html5_streams(self, video_info_url):
         video_info = self.video_info()
-        video_info_url = video_info.get("data-html5-playlist") or video_info.get("data-video-id")
-
         res = self.session.http.post(video_info_url,
-                        data=json.dumps(self.device_info),
-                        headers={"hmac": video_info.get("data-video-hmac")})
+                                     data=json.dumps(self.device_info),
+                                     headers={"hmac": video_info.get("data-video-hmac")})
         data = self.session.http.json(res, schema=self._video_info_schema)
 
         log.debug("Video ID info response: {0}".format(data))
@@ -78,6 +71,31 @@ def _get_streams(self):
             for s in HLSStream.parse_variant_playlist(self.session, url, name_fmt=name_fmt).items():
                 yield s
 
+    def _get_rtmp_streams(self, video_info_url):
+        log.debug("XML data path: {0}".format(video_info_url))
+        res = self.session.http.get(video_info_url)
+        playlist = self.session.http.xml(res, ignore_ns=True)
+        mediafiles = playlist.find(".//Playlist/VideoEntries/Video/MediaFiles")
+        playpath = mediafiles.find("./MediaFile/URL")
+        return {"live": RTMPStream(self.session, {"rtmp": mediafiles.attrib.get("base"),
+                                                  "playpath": playpath.text,
+                                                  "live": True,
+                                                  "swfVfy": self.swf_url
+                                                  })}
+
+    def _get_streams(self):
+        """
+            Find all the streams for the ITV url
+            :return: Mapping of quality to stream
+        """
+        self.session.http.headers.update({"User-Agent": useragents.FIREFOX})
+        stream = self._url_re.match(self.url).group("stream")
+        video_info = self.video_info()
+        video_info_url = video_info.get("data-video-id" if stream.lower() in ("itv", "itv4") else "data-html5-playlist")
+        if video_info_url.endswith(".xml"):
+            return self._get_rtmp_streams(video_info_url)
+        else:
+            return self._get_html5_streams(video_info_url)
 
 
 __plugin__ = ITVPlayer


Test patch: