Repository: streamlink/streamlink
Pull request #816 (2017-04-19 22:01:05)

Add support for ElTreceTV (VOD & Live)
As suggested in https://github.com/streamlink/streamlink/issues/805


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5424186c..c6871830 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,7 +24,7 @@ install:
 
 script:
   - python -m pytest tests/
-  - coverage run setup.py test
+  - coverage run -m pytest tests/
   # test building the docs
   - if [[ $BUILD_DOCS == 'yes' ]]; then make --directory=docs html; fi
   - if [[ $BUILD_INSTALLER == 'yes' ]]; then ./script/makeinstaller.sh; fi
diff --git a/appveyor.yml b/appveyor.yml
index ed6353e1..a99e2df0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -8,14 +8,12 @@ environment:
     - PYTHON: "C:\\Python36"
     - PYTHON: "C:\\Python27-x64"
     - PYTHON: "C:\\Python33-x64"
-      DISTUTILS_USE_SDK: "1"
     - PYTHON: "C:\\Python34-x64"
-      DISTUTILS_USE_SDK: "1"
     - PYTHON: "C:\\Python35-x64"
     - PYTHON: "C:\\Python36-x64"
 
 install:
-    # If there is a newer build queued for the same PR, cancel this one.
+  # If there is a newer build queued for the same PR, cancel this one.
   # The AppVeyor 'rollout builds' option is supposed to serve the same
   # purpose but it is problematic because it tends to cancel builds pushed
   # directly to master instead of just PR builds (or the converse).
@@ -40,7 +38,7 @@ install:
 
   # Upgrade to the latest version of pip to avoid it displaying warnings
   # about it being out of date.
-  - "pip install --disable-pip-version-check --user --upgrade pip"
+  - "pip install --disable-pip-version-check --user --upgrade pip setuptools"
 
   # install dev requirements, for testing, etc.
   - "pip install -r dev-requirements.txt"
diff --git a/docs/install.rst b/docs/install.rst
index fc30d34a..f7385939 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -163,8 +163,6 @@ Name                                 Notes
 **Optional**
 --------------------------------------------------------------------------------
 `RTMPDump`_                          Required to play RTMP streams.
-`python-librtmp`_                    Required by the *ustreamtv* plugin to be
-                                     able to use non-mobile streams.
 `ffmpeg`_                            Required to play streams that are made up of separate
                                      audio and video streams, eg. YouTube 1080p+
 ==================================== ===========================================
@@ -190,7 +188,6 @@ With these two environment variables it is possible to use `pycrypto`_ instead o
 .. _pycountry: https://pypi.python.org/pypi/pycountry
 .. _pycrypto: https://www.dlitz.net/software/pycrypto/
 .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/
-.. _python-librtmp: https://github.com/chrippa/python-librtmp
 .. _ffmpeg: https://www.ffmpeg.org/
 .. _iso-639: https://pypi.python.org/pypi/iso-639
 .. _iso3166: https://pypi.python.org/pypi/iso3166
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 2e7fc52b..75af8444 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -26,7 +26,6 @@ ard_mediathek       - ardmediathek.de    Yes   Yes   Streams may be geo-restrict
                     - mediathek... [5]_
 artetv              arte.tv              Yes   Yes
 atresplayer         atresplayer.com      Yes   No    Streams are geo-restricted to Spain.
-azubutv             azubu.tv             Yes   No
 bambuser            bambuser.com         Yes   Yes
 bbciplayer          bbc.co.uk/iplayer    Yes   Yes   Streams may be geo-restricted to the United Kingdom.
 beam                beam.pro             Yes   Yes
@@ -45,6 +44,7 @@ camsoda             camsoda.com          Yes   No
 canalplus           - canalplus.fr       Yes   Yes   Streams may be geo-restricted to France.
                     - c8.fr
                     - cstar.fr
+                    - cnews.fr
 canlitv             - canlitv.com        Yes   --
                     - canlitv.life
                     - canlitvlive.co
@@ -65,6 +65,7 @@ cinergroup          - showtv.com.tr      Yes   No
 connectcast         connectcast.tv       Yes   Yes
 crunchyroll         crunchyroll.com      --    Yes
 cybergame           cybergame.tv         Yes   Yes
+cyro                cyro.se              --    Yes
 dailymotion         dailymotion.com      Yes   Yes
 daisuki             daisuki.net          No    Yes
 deutschewelle       dw.com               Yes   No
@@ -91,9 +92,11 @@ dplay               - dplay.se           --    Yes   Streams may be geo-restrict
                     - dplay.dk
 drdk                dr.dk                Yes   Yes   Streams may be geo-restricted to Denmark.
 earthcam            earthcam.com         Yes   Yes   Only works for the cams hosted on EarthCam.
+eltrecetv           eltrecetv.com.ar     Yes   Yes   Streams may be geo-restricted to Argentina.
 eurocom             eurocom.bg           Yes   No
 euronews            euronews.com         Yes   No
 expressen           expressen.se         Yes   Yes
+facebook            facebook.com         Yes   No    Only 360p HLS streams.
 filmon              filmon.com           Yes   Yes   Only SD quality streams.
 filmon_us           filmon.us            Yes   Yes
 foxtr               fox.com.tr           Yes   No
@@ -134,7 +137,8 @@ npo                 - npo.nl             Yes   Yes   Streams may be geo-restrict
                     - zappelin.nl
 nrk                 - tv.nrk.no          Yes   Yes   Streams may be geo-restricted to Norway.
                     - radio.nrk.no
-oldlivestream       original.liv... [3]_ Yes   No    Only mobile streams are supported.
+oldlivestream       - original.li.. [3]_ Yes   No    Only mobile streams are supported.
+                    - cdn.livestream.com
 openrectv           openrec.tv           Yes   Yes
 orf_tvthek          tvthek.orf.at        Yes   Yes
 ovvatv              ovva.tv              Yes   No
@@ -149,6 +153,7 @@ pluzz               - pluzz.francetv.fr  Yes   Yes   Streams may be geo-restrict
 powerapp            powerapp.com.tr      Yes   No
 raiplay             raiplay.it           Yes   No    Most streams are geo-restricted to Italy.
 rtlxl               rtlxl.nl             No    Yes   Streams may be geo-restricted to The Netherlands. Livestreams not supported.
+rte                 rte.ie/player        Yes   Yes
 rtve                rtve.es              Yes   No
 rtvs                rtvs.sk              Yes   No    Streams may be geo-restricted to Slovakia.
 ruv                 ruv.is               Yes   Yes   Streams may be geo-restricted to Iceland.
@@ -176,6 +181,7 @@ svtplay             - svtplay.se         Yes   Yes   Streams may be geo-restrict
                     - oppetarkiv.se
 swisstxt            - srf.ch             Yes   No    Streams are geo-restricted to Switzerland.
                     - rsi.ch
+telefe              telefe.com           No    Yes   Streams are geo-restricted to Argentina.
 tf1                 - tf1.fr             Yes   No    Streams may be geo-restricted to France.
                     - lci.fr
 tga                 - star.plu.cn        Yes   No
@@ -191,6 +197,7 @@ turkuvaz            - atv.com.tr         Yes   No
                     - aspor.com.tr
                     - minikago.com.tr
                     - minikacocuk.com.tr
+tv1channel          tv1channel.org       Yes   Yes
 tv3cat              tv3.cat              Yes   Yes   Streams may be geo-restricted to Spain.
 tv4play             - tv4play.se         Yes   Yes   Streams may be geo-restricted to Sweden.
                                                      Only non-premium streams currently supported.
@@ -204,7 +211,7 @@ tvrby               tvr.by               Yes   No    Streams may be geo-restrict
 tvrplus             tvrplus.ro           Yes   No    Streams may be geo-restricted to Romania.
 twitch              twitch.tv            Yes   Yes   Possible to authenticate for access to
                                                      subscription streams.
-ustreamtv           ustream.tv           Yes   Yes   Currently broken.
+ustreamtv           ustream.tv           Yes   Yes
 vaughnlive          - vaughnlive.tv      Yes   --
                     - breakers.tv
                     - instagib.tv
diff --git a/src/streamlink/plugins/artetv.py b/src/streamlink/plugins/artetv.py
index 822cff52..96c048c3 100644
--- a/src/streamlink/plugins/artetv.py
+++ b/src/streamlink/plugins/artetv.py
@@ -6,18 +6,20 @@ from itertools import chain
 
 from streamlink.compat import urlparse
 from streamlink.plugin import Plugin
-from streamlink.plugin.api import http, validate
-from streamlink.stream import HDSStream, HLSStream, HTTPStream, RTMPStream
+from streamlink.plugin.api import http
+from streamlink.plugin.api import validate
+from streamlink.stream import HDSStream
+from streamlink.stream import HLSStream
+from streamlink.stream import HTTPStream
 
-SWF_URL = "http://www.arte.tv/player/v2/jwplayer6/mediaplayer.6.6.swf"
-JSON_VOD_URL = "https://api.arte.tv/api/player/v1/config/{}/{}"
-JSON_LIVE_URL = "https://api.arte.tv/api/player/v1/livestream/{}"
+JSON_VOD_URL = "https://api.arte.tv/api/player/v1/config/{0}/{1}"
+JSON_LIVE_URL = "https://api.arte.tv/api/player/v1/livestream/{0}"
 
 _url_re = re.compile(r"""
-    https?://(?:\w+\.)?arte.tv/guide/
+    https?://(?:\w+\.)?arte\.tv/(?:guide/)?
     (?P<language>[a-z]{2})/
     (?:
-        (?P<video_id>.+?)/.+ | # VOD
+        (?:videos/)?(?P<video_id>(?!RC\-|videos)[^/]+?)/.+ | # VOD
         (?:direct|live)        # Live TV
     )
 """, re.VERBOSE)
@@ -31,62 +33,57 @@ _video_schema = validate.Schema({
                     "height": int,
                     "mediaType": validate.text,
                     "url": validate.text,
-                    validate.optional("streamer"): validate.text
+                    "versionShortLibelle": validate.text
                 },
             },
-        ),
-        "VTY": validate.text
+        )
     }
 })
 
 
 class ArteTV(Plugin):
     @classmethod
-    def can_handle_url(self, url):
+    def can_handle_url(cls, url):
         return _url_re.match(url)
 
-    def _create_stream(self, stream, is_live):
+    def _create_stream(self, stream, language):
         stream_name = "{0}p".format(stream["height"])
         stream_type = stream["mediaType"]
         stream_url = stream["url"]
-
-        if stream_type in ("hls", "mp4"):
-            if urlparse(stream_url).path.endswith("m3u8"):
+        stream_language = stream["versionShortLibelle"]
+
+        if language == "de":
+            language = ["DE", "VOST-DE", "VA", "VOA", "Dt. Live"]
+        elif language == "en":
+            language = ["ANG", "VOST-ANG"]
+        elif language == "es":
+            language = ["ESP", "VOST-ESP"]
+        elif language == "fr":
+            language = ["FR", "VOST-FR", "VF", "VOF", "Frz. Live"]
+        elif language == "pl":
+            language = ["POL", "VOST-POL"]
+
+        if stream_language in language:
+            if stream_type in ("hls", "mp4"):
+                if urlparse(stream_url).path.endswith("m3u8"):
+                    try:
+                        streams = HLSStream.parse_variant_playlist(self.session, stream_url)
+
+                        for stream in streams.items():
+                            yield stream
+                    except IOError as err:
+                        self.logger.error("Failed to extract HLS streams: {0}", err)
+                else:
+                    yield stream_name, HTTPStream(self.session, stream_url)
+
+            elif stream_type == "f4m":
                 try:
-                    streams = HLSStream.parse_variant_playlist(self.session, stream_url)
+                    streams = HDSStream.parse_manifest(self.session, stream_url)
 
-                    # TODO: Replace with "yield from" when dropping Python 2.
                     for stream in streams.items():
                         yield stream
                 except IOError as err:
-                    self.logger.error("Failed to extract HLS streams: {0}", err)
-            else:
-                yield stream_name, HTTPStream(self.session, stream_url)
-
-        elif stream_type == "f4m":
-            try:
-                streams = HDSStream.parse_manifest(self.session, stream_url)
-
-                for stream in streams.items():
-                    yield stream
-            except IOError as err:
-                self.logger.error("Failed to extract HDS streams: {0}", err)
-
-        elif stream_type == "rtmp":
-            params = {
-                "rtmp": stream["streamer"],
-                "playpath": stream["url"],
-                "swfVfy": SWF_URL,
-                "pageUrl": self.url,
-            }
-
-            if is_live:
-                params["live"] = True
-            else:
-                params["playpath"] = "mp4:{0}".format(params["playpath"])
-
-            stream = RTMPStream(self.session, params)
-            yield stream_name, stream
+                    self.logger.error("Failed to extract HDS streams: {0}", err)
 
     def _get_streams(self):
         match = _url_re.match(self.url)
@@ -102,9 +99,8 @@ class ArteTV(Plugin):
         if not video["videoJsonPlayer"]["VSR"]:
             return
 
-        is_live = video["videoJsonPlayer"]["VTY"] == "LIVE"
         vsr = video["videoJsonPlayer"]["VSR"].values()
-        streams = (self._create_stream(stream, is_live) for stream in vsr)
+        streams = (self._create_stream(stream, language) for stream in vsr)
 
         return chain.from_iterable(streams)
 
diff --git a/src/streamlink/plugins/azubutv.py b/src/streamlink/plugins/azubutv.py
deleted file mode 100644
index 888993c0..00000000
--- a/src/streamlink/plugins/azubutv.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python
-import json
-import requests
-
-import re
-
-from io import BytesIO
-from time import sleep
-
-from streamlink.exceptions import PluginError
-
-from streamlink.plugin import Plugin
-from streamlink.plugin.api import http, validate
-from streamlink.stream import HLSStream
-
-
-HTTP_HEADERS = {
-    "User-Agent": ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
-                   "(KHTML, like Gecko) Chrome/36.0.1944.9 Safari/537.36"),
-    'Accept': 'application/json;pk=BCpkADawqM1gvI0oGWg8dxQHlgT8HkdE2LnAlWAZkOlznO39bSZX726u4JqnDsK3MDXcO01JxXK2tZtJbgQChxgaFzEVdHRjaDoxaOu8hHOO8NYhwdxw9BzvgkvLUlpbDNUuDoc4E4wxDToV'
-
-}
-
-_url_re = re.compile(r"http(s)?://(\w+\.)?azubu.tv/(?P<domain>\w+)")
-
-PARAMS_REGEX = r"(\w+)=({.+?}|\[.+?\]|\(.+?\)|'(?:[^'\\]|\\')*'|\"(?:[^\"\\]|\\\")*\"|\S+)"
-stream_video_url = "http://api.azubu.tv/public/channel/{}/player"
-
-
-class AzubuTV(Plugin):
-    @classmethod
-    def can_handle_url(cls, url):
-        return _url_re.match(url)
-
-    @classmethod
-    def stream_weight(cls, stream):
-        if stream == "source":
-            weight = 1080
-        else:
-            weight, group = Plugin.stream_weight(stream)
-
-        return weight, "azubutv"
-
-    def _parse_params(self, params):
-        rval = {}
-        matches = re.findall(PARAMS_REGEX, params)
-
-        for key, value in matches:
-            try:
-                value = ast.literal_eval(value)
-            except Exception:
-                pass
-
-            rval[key] = value
-
-        return rval
-
-    def _get_stream_url(self, o):
-
-        match = _url_re.match(self.url)
-        channel = match.group('domain')
-
-        channel_info = requests.get(stream_video_url.format(channel))
-        j = json.loads(channel_info.text)
-
-        if j["data"]["is_live"] is not True:
-            return "", False
-        else:
-            is_live = True
-
-        stream_url = 'https://edge.api.brightcove.com/playback/v1/accounts/3361910549001/videos/ref:{0}'
-
-        r = requests.get(stream_url.format(j["data"]["stream_video"]["reference_id"]), headers=HTTP_HEADERS)
-        t = json.loads(r.text)
-
-        stream_url = t["sources"][0]["src"]
-        return stream_url, is_live
-
-    def _get_streams(self):
-        hls_url, is_live = self._get_stream_url(self)
-
-        if not is_live:
-            return
-
-        split = self.url.split(" ")
-        params = (" ").join(split[1:])
-        params = self._parse_params(params)
-
-        try:
-            streams = HLSStream.parse_variant_playlist(self.session, hls_url, **params)
-        except IOError as err:
-            raise PluginError(err)
-
-        return streams
-
-
-__plugin__ = AzubuTV
diff --git a/src/streamlink/plugins/canalplus.py b/src/streamlink/plugins/canalplus.py
index 3d0f1b4b..0d21a61f 100644
--- a/src/streamlink/plugins/canalplus.py
+++ b/src/streamlink/plugins/canalplus.py
@@ -1,6 +1,5 @@
 import re
 
-from streamlink.exceptions import PluginError
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, useragents, validate
 from streamlink.stream import HDSStream, HLSStream, HTTPStream
@@ -8,7 +7,7 @@ from streamlink.stream import HDSStream, HLSStream, HTTPStream
 
 class CanalPlus(Plugin):
     API_URL = 'http://service.canal-plus.com/video/rest/getVideos/{0}/{1}?format=json'
-    CHANNEL_MAP = {'canalplus': 'cplus', 'c8': 'd8', 'cstar': 'd17'}
+    CHANNEL_MAP = {'canalplus': 'cplus', 'c8': 'd8', 'cstar': 'd17', 'cnews': 'itele'}
     HDCORE_VERSION = '3.1.0'
     # Secret parameter needed to download HTTP videos on canalplus.fr
     SECRET = 'pqzerjlsmdkjfoiuerhsdlfknaes'
@@ -16,13 +15,15 @@ class CanalPlus(Plugin):
     _url_re = re.compile(r'''
         http://
         (
-            www\.(?P<channel>canalplus|c8|cstar)\.fr/.*pid.+?\.html(\?(?P<video_id>[0-9]+))? |
-            replay\.(?P<replay_channel>c8|cstar)\.fr/video/(?P<replay_video_id>[0-9]+)
+            www\.(?P<channel>canalplus|c8|cstar)\.fr/(direct|.*pid.+?\.html(\?(?P<video_id>[0-9]+))?) |
+            replay\.(?P<replay_channel>c8|cstar)\.fr/video/(?P<replay_video_id>[0-9]+) |
+            www\.cnews\.fr/.+
         )
 ''', re.VERBOSE)
-    _video_id_re = re.compile(r'\bdata-video="(?P<video_id>[0-9]+)"')
+    _video_id_re = re.compile(r'(\bdata-video="|<meta property="og:video" content=".+?&videoId=)(?P<video_id>[0-9]+)"')
     _mp4_bitrate_re = re.compile(r'.*_(?P<bitrate>[0-9]+k)\.mp4')
     _api_schema = validate.Schema({
+        'ID_DM': validate.text,
         'TYPE': validate.text,
         'MEDIA': validate.Schema({
             'VIDEOS': validate.Schema({
@@ -44,25 +45,26 @@ class CanalPlus(Plugin):
     def _get_streams(self):
         # Get video ID and channel from URL
         match = self._url_re.match(self.url)
-        channel = match.group('channel')
-        if channel is None:
-            # Replay website
-            channel = match.group('replay_channel')
-            video_id = match.group('replay_video_id')
-        else:
+        channel = match.group('channel') or match.group('replay_channel') or 'cnews'
+        video_id = match.group('video_id') or match.group('replay_video_id')
+        if video_id is None:
+            # Retrieve URL page and search for video ID
+            res = http.get(self.url)
+            match = self._video_id_re.search(res.text)
+            if match is None:
+                return
             video_id = match.group('video_id')
-            if video_id is None:
-                # Retrieve URL page and search for video ID
-                res = http.get(self.url)
-                match = self._video_id_re.search(res.text)
-                if match is None:
-                    return
-                video_id = match.group('video_id')
 
         res = http.get(self.API_URL.format(self.CHANNEL_MAP[channel], video_id))
         videos = http.json(res, schema=self._api_schema)
         parsed = []
         headers = {'User-Agent': self._user_agent}
+
+        # Some videos may be also available on Dailymotion (especially on CNews)
+        if videos['ID_DM'] != '':
+            for stream in self.session.streams('https://www.dailymotion.com/video/' + videos['ID_DM']).items():
+                yield stream
+
         for quality, video_url in list(videos['MEDIA']['VIDEOS'].items()):
             # Ignore empty URLs
             if video_url == '':
@@ -75,7 +77,7 @@ class CanalPlus(Plugin):
 
             try:
                 # HDS streams don't seem to work for live videos
-                if '.f4m' in video_url and videos['TYPE'] != 'CHAINE LIVE':
+                if '.f4m' in video_url and 'LIVE' not in videos['TYPE']:
                     for stream in HDSStream.parse_manifest(self.session,
                                                            video_url,
                                                            params={'hdcore': self.HDCORE_VERSION},
@@ -97,9 +99,9 @@ class CanalPlus(Plugin):
                                               video_url,
                                               params={'secret': self.SECRET},
                                               headers=headers)
-            except PluginError:
-                self.logger.error('Failed to access stream, may be due to geo-restriction')
-                raise
+            except IOError as err:
+                if '403 Client Error' in str(err):
+                    self.logger.error('Failed to access stream, may be due to geo-restriction')
 
 
 __plugin__ = CanalPlus
diff --git a/src/streamlink/plugins/cyro.py b/src/streamlink/plugins/cyro.py
new file mode 100644
index 00000000..7df1cfaa
--- /dev/null
+++ b/src/streamlink/plugins/cyro.py
@@ -0,0 +1,32 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+
+
+_url_re = re.compile(r'''https?://cyro\.se/watch/''')
+_embed_re = re.compile(r'''<iframe.+src="([^"]+)"''')
+
+
+class Cyro(Plugin):
+    @classmethod
+    def can_handle_url(cls, url):
+        return _url_re.match(url)
+
+    def _get_streams(self):
+        res = http.get(self.url)
+        match = _embed_re.search(res.text)
+        if match:
+            iframe_url = "https://cyro.se{0}".format(match.group(1))
+            res = http.get(iframe_url)
+            match = _embed_re.search(res.text)
+            if match:
+                iframe_url = "https://cyro.se{0}".format(match.group(1))
+                res = http.get(iframe_url)
+                match = _embed_re.search(res.text)
+                if match:
+                    url = match.group(1)
+                    return self.session.streams(url)
+
+
+__plugin__ = Cyro
diff --git a/src/streamlink/plugins/eltrecetv.py b/src/streamlink/plugins/eltrecetv.py
new file mode 100644
index 00000000..d922a3a7
--- /dev/null
+++ b/src/streamlink/plugins/eltrecetv.py
@@ -0,0 +1,39 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, useragents, validate
+from streamlink.stream import HLSStream, HTTPStream
+from streamlink.utils import parse_json
+
+class ElTreceTV(Plugin):
+    _url_re = re.compile(r'https?://(?:www\.)?eltrecetv.com.ar/.+')
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url)
+
+    def _get_streams(self):
+
+        video_url_found_hls = ""
+
+        if "eltrecetv.com.ar/vivo" in self.url.lower():
+            http.headers = {'Referer': self.url,
+            'User-Agent': "ArtearPlayer/3.2.42 (Linux;Android 4.4.2) ExoPlayerLib/1.5.9"}
+            video_url_found_hls = "http://stream.eltrecetv.com.ar/live13/13tv/13tv1/playlist.m3u8"
+        else:
+            http.headers = {'Referer': self.url,
+            'User-Agent': useragents.CHROME}
+            res = http.get(self.url)
+            _player_re = re.compile(r'''data-kaltura="([^"]+)"''')
+            match = _player_re.search(res.text)
+            if not match:
+                return
+            json_video_search = parse_json(match.group(1).replace("&quot;", '"'))
+            video_url_found_hls = "https://vodgc.com/p/111/sp/11100/playManifest/entryId/" + json_video_search["entryId"] + "/format/applehttp/protocol/https/a.m3u8"
+
+        if video_url_found_hls:
+            hls_streams = HLSStream.parse_variant_playlist(self.session, video_url_found_hls)
+            for s in hls_streams.items():
+                yield s
+
+__plugin__ = ElTreceTV
diff --git a/src/streamlink/plugins/facebook.py b/src/streamlink/plugins/facebook.py
new file mode 100644
index 00000000..ec1220d6
--- /dev/null
+++ b/src/streamlink/plugins/facebook.py
@@ -0,0 +1,24 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.stream import HLSStream
+
+_playlist_url = "https://www.facebook.com/video/playback/playlist.m3u8?v={0}"
+
+_url_re = re.compile(r"http(s)?://(www\.)?facebook\.com/[^/]+/videos/(?P<video_id>\d+)")
+
+
+class Facebook(Plugin):
+    @classmethod
+    def can_handle_url(cls, url):
+        return _url_re.match(url)
+
+    def _get_streams(self):
+        match = _url_re.match(self.url)
+        video = match.group("video_id")
+
+        playlist = _playlist_url.format(video)
+
+        return HLSStream.parse_variant_playlist(self.session, playlist)
+
+__plugin__ = Facebook
diff --git a/src/streamlink/plugins/oldlivestream.py b/src/streamlink/plugins/oldlivestream.py
index 3170209a..7dfdaa6a 100644
--- a/src/streamlink/plugins/oldlivestream.py
+++ b/src/streamlink/plugins/oldlivestream.py
@@ -5,7 +5,7 @@ from streamlink.stream import HLSStream
 
 PLAYLIST_URL = "http://x{0}x.api.channel.livestream.com/3.0/playlist.m3u8"
 
-_url_re = re.compile(r"http(s)?://original.livestream.com/(?P<channel>[^&?/]+)")
+_url_re = re.compile(r"http(s)?://(cdn|original)\.livestream\.com/(embed/)?(?P<channel>[^&?/]+)")
 
 
 class OldLivestream(Plugin):
diff --git a/src/streamlink/plugins/pcyourfreetv.py b/src/streamlink/plugins/pcyourfreetv.py
index 912a31c8..968ac7d3 100644
--- a/src/streamlink/plugins/pcyourfreetv.py
+++ b/src/streamlink/plugins/pcyourfreetv.py
@@ -1,13 +1,18 @@
 import re
 
+from streamlink.compat import parse_qsl, unquote
 from streamlink.plugin import Plugin, PluginOptions
 from streamlink.plugin.api import http
 from streamlink.stream import HLSStream
 
 
 class PCYourFreeTV(Plugin):
+    LIVE_TV_URL = 'http://pc-yourfreetv.com/indexlivetv.php?page_id=1'
+
     _login_url = 'http://pc-yourfreetv.com/home.php'
-    _url_re = re.compile(r'http://pc-yourfreetv\.com/index_player\.php\?channel=.+?&page_id=\d+')
+    _url_re = re.compile(r'http://pc-yourfreetv\.com/indexplayer\.php\?channel=.+?&page_id=\d+')
+    _token_re = re.compile(r'\bsrc="indexplayer\.php\?channel=.+?&(?P<tokens>.+?)"')
+    _player_re = re.compile(r"<script language=JavaScript>m='(?P<player>.+?)'", re.DOTALL)
     _video_url_re = re.compile(r"jwplayer\('.+?'\)\.setup\({.+?file: \"(?P<video_url>[^\"]+?)\".+?}\);", re.DOTALL)
 
     options = PluginOptions({
@@ -36,18 +41,33 @@ class PCYourFreeTV(Plugin):
         password = self.get_option('password')
 
         if username is None or password is None:
-            self.logger.error("PC-YourFreeTV requires authentication, use --pcyourfreetv-username"
+            self.logger.error("PC-YourFreeTV requires authentication, use --pcyourfreetv-username "
                               "and --pcyourfreetv-password to set your username/password combination")
             return
 
         if self.login(username, password):
             self.logger.info("Successfully logged in as {0}", username)
 
+        # Get a fresh authorization token
+        res = http.get(self.LIVE_TV_URL)
+        match = self._token_re.search(res.text)
+        if match is None:
+            return
+
         # Retrieve URL page and search for stream data
-        res = http.get(self.url)
-        match = self._video_url_re.search(res.text)
+        res = http.get(self.url, params=parse_qsl(match.group('tokens')))
+        match = self._player_re.search(res.text)
         if match is None:
             return
+
+        while match is not None:
+            player = unquote(match.group('player'))
+            match = self._player_re.search(player)
+
+        match = self._video_url_re.search(player)
+        if match is None:
+            return
+
         video_url = match.group('video_url')
         if '.m3u8' in video_url:
             streams = HLSStream.parse_variant_playlist(self.session, video_url)
diff --git a/src/streamlink/plugins/rte.py b/src/streamlink/plugins/rte.py
new file mode 100644
index 00000000..04fb9824
--- /dev/null
+++ b/src/streamlink/plugins/rte.py
@@ -0,0 +1,110 @@
+from datetime import datetime
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, validate
+from streamlink.stream import HDSStream, HLSStream
+
+
+class RTE(Plugin):
+    VOD_API_URL = 'http://www.rte.ie/rteavgen/getplaylist/?type=web&format=json&id={0}'
+    LIVE_API_URL = 'http://feeds.rasset.ie/livelistings/playlist'
+
+    _url_re = re.compile(r'http://www\.rte\.ie/player/[a-z0-9]+/(?:show/[a-z-]+-[0-9]+/(?P<video_id>[0-9]+)|live/(?P<channel_id>[0-9]+))')
+
+    _vod_api_schema = validate.Schema({
+        'current_date': validate.text,
+        'shows': validate.Schema(
+            list,
+            validate.length(1),
+            validate.get(0),
+            validate.Schema({
+                'valid_start': validate.text,
+                'valid_end': validate.text,
+                'media:group': validate.Schema(
+                    list,
+                    validate.length(1),
+                    validate.get(0),
+                    validate.Schema(
+                        {
+                            'hls_server': validate.url(),
+                            'hls_url': validate.text,
+                            'hds_server': validate.url(),
+                            'hds_url': validate.text,
+                            # API returns RTMP streams that don't seem to work, ignore them
+                            # 'url': validate.any(
+                            #     validate.url(scheme="rtmp"),
+                            #     validate.url(scheme="rtmpe")
+                            # )
+                        },
+                        validate.transform(lambda x: [x['hls_server'] + x['hls_url'], x['hds_server'] + x['hds_url']])
+                    ),
+                ),
+            }),
+        )
+    })
+
+    _live_api_schema = validate.Schema(
+        validate.xml_findall('.//{http://search.yahoo.com/mrss/}content'),
+        [
+            validate.all(
+                validate.xml_element(attrib={'url': validate.url()}),
+                validate.get('url')
+            )
+        ]
+    )
+    _live_api_iphone_schema = validate.Schema(
+        list,
+        validate.length(1),
+        validate.get(0),
+        validate.Schema(
+            {'fullUrl': validate.any(validate.url(), 'none')},
+            validate.get('fullUrl')
+        )
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return RTE._url_re.match(url)
+
+    def _get_streams(self):
+        match = self._url_re.match(self.url)
+        video_id = match.group('video_id')
+
+        if video_id is not None:
+            # VOD
+            res = http.get(self.VOD_API_URL.format(video_id))
+            stream_data = http.json(res, schema=self._vod_api_schema)
+
+            # Check whether video format is expired
+            current_date = datetime.strptime(stream_data['current_date'], '%Y-%m-%dT%H:%M:%S.%f')
+            valid_start = datetime.strptime(stream_data['shows']['valid_start'], '%Y-%m-%dT%H:%M:%S')
+            valid_end = datetime.strptime(stream_data['shows']['valid_end'], '%Y-%m-%dT%H:%M:%S')
+            if current_date < valid_start or current_date > valid_end:
+                self.logger.error('Failed to access stream, may be due to expired content')
+                return
+
+            streams = stream_data['shows']['media:group']
+        else:
+            # Live
+            channel_id = match.group('channel_id')
+            # Get live streams for desktop
+            res = http.get(self.LIVE_API_URL, params={'channelid': channel_id})
+            streams = http.xml(res, schema=self._live_api_schema)
+
+            # Get HLS streams for Iphone
+            res = http.get(self.LIVE_API_URL, params={'channelid': channel_id, 'platform': 'iphone'})
+            stream = http.json(res, schema=self._live_api_iphone_schema)
+            if stream != 'none':
+                streams.append(stream)
+
+        for stream in streams:
+            if '.f4m' in stream:
+                for s in HDSStream.parse_manifest(self.session, stream).items():
+                    yield s
+            if '.m3u8' in stream:
+                for s in HLSStream.parse_variant_playlist(self.session, stream).items():
+                    yield s
+
+
+__plugin__ = RTE
diff --git a/src/streamlink/plugins/showroom.py b/src/streamlink/plugins/showroom.py
index 4618f07b..289e0074 100644
--- a/src/streamlink/plugins/showroom.py
+++ b/src/streamlink/plugins/showroom.py
@@ -2,7 +2,7 @@
 import re
 
 from streamlink.plugin import Plugin
-from streamlink.plugin.api import http, validate
+from streamlink.plugin.api import http, validate, useragents
 from streamlink.stream import RTMPStream
 
 _url_re = re.compile(r'''^https?://
@@ -74,11 +74,6 @@ _info_pages = set((
 
 
 class Showroom(Plugin):
-    @staticmethod
-    def _get_stream_info(room_id):
-        res = http.get(_api_data_url.format(room_id=room_id))
-        return http.json(res, schema=_api_data_schema)
-
     @classmethod
     def can_handle_url(cls, url):
         match = _url_re.match(url)
@@ -95,6 +90,10 @@ class Showroom(Plugin):
 
     def __init__(self, url):
         Plugin.__init__(self, url)
+        self._headers = {
+            'Referer': self.url,
+            'User-Agent': useragents.FIREFOX
+        }
         self._room_id = None
         self._info = None
         self._title = None
@@ -112,6 +111,10 @@ class Showroom(Plugin):
             self._room_id = self._get_room_id()
         return self._room_id
 
+    def _get_stream_info(self, room_id):
+        res = http.get(_api_data_url.format(room_id=room_id), headers=self._headers)
+        return http.json(res, schema=_api_data_schema)
+
     def _get_room_id(self):
         """
         Locates unique identifier ("room_id") for the room.
@@ -123,7 +126,7 @@ class Showroom(Plugin):
         if match_dict['room_id'] is not None:
             return match_dict['room_id']
         else:
-            res = http.get(self.url)
+            res = http.get(self.url, headers=self._headers)
             match = _room_id_re.search(res.text)
             if not match:
                 title = self.url.rsplit('/', 1)[-1]
diff --git a/src/streamlink/plugins/telefe.py b/src/streamlink/plugins/telefe.py
new file mode 100644
index 00000000..fd19b63a
--- /dev/null
+++ b/src/streamlink/plugins/telefe.py
@@ -0,0 +1,48 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, useragents, validate
+from streamlink.stream import HLSStream, HTTPStream
+from streamlink.utils import parse_json
+
+
+class Telefe(Plugin):
+    _url_re = re.compile(r'https?://telefe.com/.+')
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url)
+
+    def _get_streams(self):
+        res = http.get(self.url, headers={'User-Agent': useragents.CHROME})
+        video_search = res.text
+        video_search = video_search[video_search.index('{"top":{"view":"PlayerContainer","model":{'):]
+        video_search = video_search[: video_search.index('}]}}') +4] + "}"
+
+        video_url_found_hls  = ""
+        video_url_found_http = ""
+
+        json_video_search = parse_json(video_search)
+        json_video_search_sources = json_video_search["top"]["model"]["videos"][0]["sources"]
+        self.logger.debug('Video ID found: {0}', json_video_search["top"]["model"]["id"])
+        for current_video_source in json_video_search_sources:
+            if "HLS" in current_video_source["type"]:
+                video_url_found_hls = "http://telefe.com" + current_video_source["url"]
+                self.logger.debug("HLS content available")
+            if "HTTP" in current_video_source["type"]:
+                video_url_found_http = "http://telefe.com" + current_video_source["url"]
+                self.logger.debug("HTTP content available")
+
+        http.headers = {'Referer': self.url,
+            'User-Agent': useragents.CHROME,
+            'X-Requested-With': 'ShockwaveFlash/25.0.0.148'}
+
+        if video_url_found_hls:
+            hls_streams = HLSStream.parse_variant_playlist(self.session, video_url_found_hls)
+            for s in hls_streams.items():
+                yield s
+
+        if video_url_found_http:
+            yield "http", HTTPStream(self.session, video_url_found_http)
+
+__plugin__ = Telefe
\ No newline at end of file
diff --git a/src/streamlink/plugins/tv1channel.py b/src/streamlink/plugins/tv1channel.py
new file mode 100644
index 00000000..377e4815
--- /dev/null
+++ b/src/streamlink/plugins/tv1channel.py
@@ -0,0 +1,26 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+
+_url_re = re.compile(r'''https?://(www\.)?tv1channel\.org/''')
+_embed_re = re.compile(r'''<iframe.+src="([^"]+)"''')
+
+
+class TV1Channel(Plugin):
+    @classmethod
+    def can_handle_url(cls, url):
+        return _url_re.match(url)
+
+    def _get_streams(self):
+        res = http.get(self.url)
+        match = _embed_re.search(res.text)
+
+        if match:
+            url = match.group(1)
+            if url.startswith('//'):
+                url = 'https:{0}'.format(url)
+            return self.session.streams(url)
+
+
+__plugin__ = TV1Channel
diff --git a/src/streamlink/plugins/tvplayer.py b/src/streamlink/plugins/tvplayer.py
index a7feeda7..13464272 100644
--- a/src/streamlink/plugins/tvplayer.py
+++ b/src/streamlink/plugins/tvplayer.py
@@ -8,25 +8,32 @@ from streamlink.stream import HLSStream
 
 
 class TVPlayer(Plugin):
+    context_url = "http://tvplayer.com/watch/context"
     api_url = "http://api.tvplayer.com/api/v2/stream/live"
     login_url = "https://tvplayer.com/account/login"
     update_url = "https://tvplayer.com/account/update-detail"
     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'var\s+(validate|platform|resourceId|token)\s+=\s*(.*?);', re.S)
+    stream_attrs_re = re.compile(r'data-(resource|token)\s*=\s*"(.*?)"', re.S)
     login_token_re = re.compile(r'input.*?name="token".*?value="(\w+)"')
     stream_schema = validate.Schema({
         "tvplayer": validate.Schema({
             "status": u'200 OK',
             "response": validate.Schema({
-                    "stream": validate.url(scheme=validate.any("http")),
-                    validate.optional("drmToken"): validate.any(None, validate.text)
-                })
+                "stream": validate.url(scheme=validate.any("http")),
+                validate.optional("drmToken"): validate.any(None, validate.text)
             })
-        },
+        })
+    },
         validate.get("tvplayer"),
         validate.get("response"))
+    context_schema = validate.Schema({
+        "validate": validate.text,
+        "platform": {
+            "key": validate.text
+        }
+    })
     options = PluginOptions({
         "email": None,
         "password": None
@@ -50,6 +57,21 @@ 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):
+        # Get the context info (validation token and platform)
+        context_res = http.get(self.context_url, params={"resource": resource,
+                                                         "nonce": token})
+        context_data = http.json(context_res, schema=self.context_schema)
+
+        # get the stream urls
+        res = http.post(self.api_url, data=dict(
+            service=service,
+            id=resource,
+            validate=context_data["validate"],
+            platform=context_data["platform"]["key"]))
+
+        return http.json(res, schema=self.stream_schema)
+
     def _get_streams(self):
         if self.get_option("email") and self.get_option("password"):
             self.authenticate(self.get_option("email"), self.get_option("password"))
@@ -67,25 +89,19 @@ class TVPlayer(Plugin):
 
         stream_attrs = dict((k, v.strip('"')) for k, v in self.stream_attrs_re.findall(res.text))
 
-        if "resourceId" in stream_attrs and "validate" in stream_attrs and "platform" in stream_attrs:
-            # get the stream urls
-            res = http.post(self.api_url, data=dict(
-                service=1,
-                id=stream_attrs["resourceId"],
-                validate=stream_attrs["validate"],
-                platform=stream_attrs["platform"],
-                token=stream_attrs.get("token")))
-
-            stream_data = http.json(res, schema=self.stream_schema)
-
-            if stream_data.get("drmToken"):
-                self.logger.error("This stream is protected by DRM can cannot be played")
-                return
-            else:
-                return HLSStream.parse_variant_playlist(self.session, stream_data["stream"])
+        if "resource" in stream_attrs and "token" in stream_attrs:
+            stream_data = self._get_stream_data(**stream_attrs)
+
+            if stream_data:
+                if stream_data.get("drmToken"):
+                    self.logger.error("This stream is protected by DRM can cannot be played")
+                    return
+                else:
+                    return HLSStream.parse_variant_playlist(self.session, stream_data["stream"])
         else:
             if "need to login" in res.text:
-                self.logger.error("You need to login using --tvplayer-email/--tvplayer-password to view this stream")
+                self.logger.error(
+                    "You need to login using --tvplayer-email/--tvplayer-password to view this stream")
 
 
 __plugin__ = TVPlayer
diff --git a/src/streamlink/plugins/ustreamtv.py b/src/streamlink/plugins/ustreamtv.py
index e059b863..bc4b2dc8 100644
--- a/src/streamlink/plugins/ustreamtv.py
+++ b/src/streamlink/plugins/ustreamtv.py
@@ -1,627 +1,265 @@
 import re
-
-from collections import namedtuple
-from functools import partial
 from random import randint
-from time import sleep
+from threading import Thread, Event
+
+import time
 
-from streamlink.compat import urlparse, urljoin, range
-from streamlink.exceptions import StreamError, PluginError, NoStreamsError
+from streamlink import PluginError
+from streamlink.compat import urljoin
 from streamlink.plugin import Plugin, PluginOptions
-from streamlink.plugin.api import http, validate
-from streamlink.stream import RTMPStream, HLSStream, HTTPStream, Stream
-from streamlink.stream.flvconcat import FLVTagConcat
-from streamlink.stream.segmented import (
-    SegmentedStreamReader, SegmentedStreamWriter, SegmentedStreamWorker
-)
-
-try:
-    import librtmp
-    HAS_LIBRTMP = True
-except ImportError:
-    HAS_LIBRTMP = False
-
-_url_re = re.compile(r"""
-    http(s)?://(www\.)?ustream.tv
-    (?:
-        (/embed/|/channel/id/)(?P<channel_id>\d+)
-    )?
-    (?:
-        /recorded/(?P<video_id>\d+)
-    )?
-""", re.VERBOSE)
-_channel_id_re = re.compile(r"\"channelId\":(\d+)")
-
-HLS_PLAYLIST_URL = (
-    "http://iphone-streaming.ustream.tv"
-    "/uhls/{0}/streams/live/iphone/playlist.m3u8"
-)
-RECORDED_URL = "http://tcdn.ustream.tv/video/{0}"
-RTMP_URL = "rtmp://r{0}-1-{1}-channel-live.ums.ustream.tv:1935/ustream"
-SWF_URL = "http://static-cdn1.ustream.tv/swf/live/viewer.rsl:505.swf"
-
-_module_info_schema = validate.Schema(
-    list,
-    validate.length(1),
-    validate.get(0),
-    dict
-)
-_amf3_array = validate.Schema(
-    validate.any(
-        validate.all(
-            {int: object},
-            validate.transform(lambda a: list(a.values())),
-        ),
-        list
-    )
-)
-_recorded_schema = validate.Schema({
-    validate.optional("stream"): validate.all(
-        _amf3_array,
-        [{
-            "name": validate.text,
-            "streams": validate.all(
-                _amf3_array,
-                [{
-                    "streamName": validate.text,
-                    "bitrate": float,
-                }],
-            ),
-            validate.optional("url"): validate.text,
-        }]
-    )
-})
-_stream_schema = validate.Schema(
-    validate.any({
-        "name": validate.text,
-        "url": validate.text,
-        "streams": validate.all(
-            _amf3_array,
-            [{
-                "chunkId": validate.any(int, float),
-                "chunkRange": {validate.text: validate.text},
-                "chunkTime": validate.any(int, float),
-                "offset": validate.any(int, float),
-                "offsetInMs": validate.any(int, float),
-                "streamName": validate.text,
-                validate.optional("bitrate"): validate.any(int, float),
-                validate.optional("height"): validate.any(int, float),
-                validate.optional("description"): validate.text,
-                validate.optional("isTranscoded"): bool
-            }],
-        )
-    },
-        {
-        "name": validate.text,
-        "varnishUrl": validate.text
-    })
-)
-_channel_schema = validate.Schema({
-    validate.optional("stream"): validate.any(
-        validate.all(
-            _amf3_array,
-            [_stream_schema],
-        ),
-        "offline"
+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.stream.hls import HLSStreamReader
+
+
+class ModuleInfoNoStreams(Exception):
+    pass
+
+
+class UHSClient(object):
+    """
+    API Client, reverse engineered by observing the interactions
+    between the web browser and the ustream servers.
+    """
+    API_URL = "http://r{0}-1-{1}-{2}-{3}.ums.ustream.tv"
+    APP_ID, APP_VERSION = 2, 1
+    api_schama = validate.Schema([{
+        "args": [object],
+        "cmd": validate.text
+    }])
+    connect_schama = validate.Schema([{
+        "args": validate.all([{"host": validate.text, "connectionId": validate.text}], validate.length(1)),
+        "cmd": "tracking"
+    }], validate.length(1), validate.get(0), validate.get("args"), validate.get(0))
+    module_info_schema = validate.Schema(
+        [validate.get("stream")],
+        validate.filter(lambda r: r is not None)
     )
-})
-
-Chunk = namedtuple("Chunk", "num url offset")
-
-
-if HAS_LIBRTMP:
-    from io import BytesIO
-    from time import time
-
-    from librtmp.rtmp import RTMPTimeoutError, PACKET_TYPE_INVOKE
-    from streamlink.packages.flashmedia.types import AMF0Value
-
-    def decode_amf(body):
-        def generator():
-            fd = BytesIO(body)
-            while True:
-                try:
-                    yield AMF0Value.read(fd)
-                except IOError:
-                    break
-
-        return list(generator())
-
-    class FlashmediaRTMP(librtmp.RTMP):
-        """RTMP connection using python-flashmedia's AMF decoder.
-
-        TODO: Move to python-librtmp instead.
-        """
-
-        def process_packets(self, transaction_id=None, invoked_method=None,
-                            timeout=None):
-            start = time()
-
-            while self.connected and transaction_id not in self._invoke_results:
-                if timeout and (time() - start) >= timeout:
-                    raise RTMPTimeoutError("Timeout")
-
-                packet = self.read_packet()
-                if packet.type == PACKET_TYPE_INVOKE:
-                    try:
-                        decoded = decode_amf(packet.body)
-                    except IOError:
-                        continue
-
-                    try:
-                        method, transaction_id_, obj = decoded[:3]
-                        args = decoded[3:]
-                    except ValueError:
-                        continue
-
-                    if method == "_result":
-                        if len(args) > 0:
-                            result = args[0]
-                        else:
-                            result = None
-
-                        self._invoke_results[transaction_id_] = result
-                    else:
-                        handler = self._invoke_handlers.get(method)
-                        if handler:
-                            res = handler(*args)
-                            if res is not None:
-                                self.call("_result", res,
-                                          transaction_id=transaction_id_)
-
-                        if method == invoked_method:
-                            self._invoke_args[invoked_method] = args
-                            break
-
-                    if transaction_id_ == 1.0:
-                        self._connect_result = packet
-                    else:
-                        self.handle_packet(packet)
-                else:
-                    self.handle_packet(packet)
-
-            if transaction_id:
-                result = self._invoke_results.pop(transaction_id, None)
-
-                return result
-
-            if invoked_method:
-                args = self._invoke_args.pop(invoked_method, None)
-
-                return args
-
-
-def create_ums_connection(app, media_id, page_url, password,
-                          exception=PluginError):
-    url = RTMP_URL.format(randint(0, 0xffffff), media_id)
-    params = {
-        "application": app,
-        "media": str(media_id),
-        "password": password
-    }
-    conn = FlashmediaRTMP(url,
-                          swfurl=SWF_URL,
-                          pageurl=page_url,
-                          connect_data=params)
-
-    try:
-        conn.connect()
-    except librtmp.RTMPError:
-        raise exception("Failed to connect to RTMP server")
-
-    return conn
-
-
-class UHSStreamWriter(SegmentedStreamWriter):
-    def __init__(self, *args, **kwargs):
-        SegmentedStreamWriter.__init__(self, *args, **kwargs)
-
-        self.concater = FLVTagConcat(flatten_timestamps=True,
-                                     sync_headers=True)
-
-    def fetch(self, chunk, retries=None):
-        if not retries or self.closed:
-            return
-
-        try:
-            params = {}
-            if chunk.offset:
-                params["start"] = chunk.offset
-
-            return http.get(chunk.url,
-                            timeout=self.timeout,
-                            params=params,
-                            exception=StreamError)
-        except StreamError as err:
-            self.logger.error("Failed to open chunk {0}: {1}", chunk.num, err)
-            return self.fetch(chunk, retries - 1)
-
-    def write(self, chunk, res, chunk_size=8192):
-        try:
-            for data in self.concater.iter_chunks(buf=res.content,
-                                                  skip_header=not chunk.offset):
-                self.reader.buffer.write(data)
-
-                if self.closed:
-                    break
-            else:
-                self.logger.debug("Download of chunk {0} complete", chunk.num)
-        except IOError as err:
-            self.logger.error("Failed to read chunk {0}: {1}", chunk.num, err)
-
-
-class UHSStreamWorker(SegmentedStreamWorker):
-    def __init__(self, *args, **kwargs):
-        SegmentedStreamWorker.__init__(self, *args, **kwargs)
-
-        self.chunk_ranges = {}
-        self.chunk_id = None
-        self.chunk_id_max = None
-        self.chunks = []
-        self.filename_format = ""
-        self.module_info_reload_time = 2
-        self.process_module_info()
-
-    def fetch_module_info(self):
-        self.logger.debug("Fetching module info")
-        conn = create_ums_connection("channel",
-                                     self.stream.channel_id,
-                                     self.stream.page_url,
-                                     self.stream.password,
-                                     exception=StreamError)
-
-        try:
-            result = conn.process_packets(invoked_method="moduleInfo",
-                                          timeout=10)
-        except (IOError, librtmp.RTMPError) as err:
-            raise StreamError("Failed to get module info: {0}".format(err))
-        finally:
-            conn.close()
-
-        result = _module_info_schema.validate(result)
-        return _channel_schema.validate(result, "module info")
-
-    def process_module_info(self):
-        if self.closed:
-            return
 
+    def __init__(self, session, media_id, application, **options):
+        self.session = session
+        http.headers.update({"User-Agent": useragents.IPHONE_6})
+        self.logger = session.logger.new_module("plugin.ustream.apiclient")
+        self.media_id = media_id
+        self.application = application
+        self.referrer = options.pop("referrer", None)
+        self._host = None
+        self.rsid = self.generate_rsid()
+        self.rpin = self.generate_rpin()
+        self._connection_id = None
+        self._app_id = options.pop("app_id", self.APP_ID)
+        self._app_version = options.pop("app_version", self.APP_VERSION)
+        self._cluster = options.pop("cluster", "live")
+        self._password = options.pop("password")
+
+    def connect(self, **options):
+        result = self.send_command(type="viewer", appId=self._app_id,
+                                   appVersion=self._app_version,
+                                   rsid=self.rsid,
+                                   rpin=self.rpin,
+                                   referrer=self.referrer,
+                                   media=str(self.media_id),
+                                   application=self.application,
+                                   schema=self.connect_schama,
+                                   password=self._password)
+
+        self._host = "http://{0}".format(result["host"])
+        self._connection_id = result["connectionId"]
+        self.logger.debug("Got new host={0}, and connectionId={1}", self._host, self._connection_id)
+        return True
+
+    def poll(self, schema=None, retries=5, timeout=5.0):
+        stime = time.time()
         try:
-            result = self.fetch_module_info()
+            r = self.send_command(connectionId=self._connection_id,
+                                  schema=schema,
+                                  retries=retries,
+                                  timeout=timeout)
         except PluginError as err:
-            self.logger.error("{0}", err)
-            return
-
-        providers = result.get("stream")
-        if not providers or providers == "offline":
-            self.logger.debug("Stream went offline")
-            self.close()
-            return
-
-        for provider in providers:
-            if provider.get("name") == self.stream.provider:
-                break
+            self.logger.debug("poll took {0:.2f}s: {1}", time.time() - stime, err)
         else:
-            return
-
-        try:
-            stream = provider["streams"][self.stream.stream_index]
-        except IndexError:
-            self.logger.error("Stream index not in result")
-            return
-
-        filename_format = stream["streamName"].replace("%", "%s")
-        filename_format = urljoin(provider["url"], filename_format)
-
-        self.filename_format = filename_format
-        self.update_chunk_info(stream)
-
-    def update_chunk_info(self, result):
-        chunk_range = result["chunkRange"]
-        if not chunk_range:
-            return
-
-        chunk_id = int(result["chunkId"])
-        chunk_offset = int(result["offset"])
-        chunk_range = dict(map(partial(map, int), chunk_range.items()))
-
-        self.chunk_ranges.update(chunk_range)
-        self.chunk_id_min = sorted(chunk_range)[0]
-        self.chunk_id_max = int(result["chunkId"])
-        self.chunks = [Chunk(i, self.format_chunk_url(i),
-                             not self.chunk_id and i == chunk_id and chunk_offset)
-                       for i in range(self.chunk_id_min, self.chunk_id_max + 1)]
-
-        if self.chunk_id is None and self.chunks:
-            self.chunk_id = chunk_id
-
-    def format_chunk_url(self, chunk_id):
-        chunk_hash = ""
-        for chunk_start in sorted(self.chunk_ranges):
-            if chunk_id >= chunk_start:
-                chunk_hash = self.chunk_ranges[chunk_start]
-
-        return self.filename_format % (chunk_id, chunk_hash)
-
-    def valid_chunk(self, chunk):
-        return self.chunk_id and chunk.num >= self.chunk_id
-
-    def iter_segments(self):
-        while not self.closed:
-            for chunk in filter(self.valid_chunk, self.chunks):
-                self.logger.debug("Adding chunk {0} to queue", chunk.num)
-                yield chunk
-
-                # End of stream
-                if self.closed:
-                    return
-
-                self.chunk_id = chunk.num + 1
-
-            if self.wait(self.module_info_reload_time):
-                try:
-                    self.process_module_info()
-                except StreamError as err:
-                    self.logger.warning("Failed to process module info: {0}", err)
-
-
-class UHSStreamReader(SegmentedStreamReader):
-    __worker__ = UHSStreamWorker
-    __writer__ = UHSStreamWriter
-
-    def __init__(self, stream, *args, **kwargs):
-        self.logger = stream.session.logger.new_module("stream.uhs")
-
-        SegmentedStreamReader.__init__(self, stream, *args, **kwargs)
-
-
-class UHSStream(Stream):
-    __shortname__ = "uhs"
-
-    def __init__(self, session, channel_id, page_url, provider,
-                 stream_index, password=""):
-        Stream.__init__(self, session)
-
-        self.channel_id = channel_id
-        self.page_url = page_url
-        self.provider = provider
-        self.stream_index = stream_index
-        self.password = password
-
-    def __repr__(self):
-        return "<UHSStream({0!r}, {1!r}, {2!r}, {3!r}, {4!r})>".format(
-            self.channel_id, self.page_url, self.provider,
-            self.stream_index, self.password
-        )
-
-    def __json__(self):
-        json = Stream.__json__(self)
-        json.update({
-            "channel_id": self.channel_id,
-            "page_url": self.page_url,
-            "provider": self.provider,
-            "stream_index": self.stream_index,
-            "password": self.password
-        })
-        return json
+            self.logger.debug("poll took {0:.2f}s", time.time() - stime)
+            return r
+
+    def generate_rsid(self):
+        return "{0:x}:{1:x}".format(randint(0, 1e10), randint(0, 1e10))
+
+    def generate_rpin(self):
+        return "_rpin.{0}".format(randint(0, 1e15))
+
+    def send_command(self, schema=None, retries=5, timeout=5.0, **args):
+        res = http.get(self.host,
+                       params=args,
+                       headers={"Referer": self.referrer,
+                                "User-Agent": useragents.IPHONE_6},
+                       retries=retries,
+                       timeout=timeout,
+                       retry_max_backoff=0.5)
+        return http.json(res, schema=schema or self.api_schama)
+
+    @property
+    def host(self):
+        host = self._host or self.API_URL.format(randint(0, 0xffffff), self.media_id, self.application,
+                                                 "lp-" + self._cluster)
+        return urljoin(host, "/1/ustream")
+
+
+class UStreamHLSStream(HLSStream):
+    class APIPoller(Thread):
+        """
+        Poll the UStream API so that stream URLs stay valid, otherwise they expire after 30 seconds.
+        """
+        def __init__(self, api, interval=10.0):
+            Thread.__init__(self)
+            self.stopped = Event()
+            self.api = api
+            self.interval = interval
+
+        def stop(self):
+            self.stopped.set()
+
+        def run(self):
+            while not self.stopped.wait(1.0):
+                res = self.api.poll(retries=30, timeout=self.interval)
+                if not res:
+                    continue
+                for cmd_args in res:
+                    self.api.logger.debug("poll response: {0}", cmd_args)
+                    if cmd_args["cmd"] == "warning":
+                        self.api.logger.warning("{code}: {message}", **cmd_args["args"])
+
+
+    def __init__(self, session_, url, api, force_restart=False, **args):
+        super(UStreamHLSStream, self).__init__(session_, url, force_restart, **args)
+        self.logger = session_.logger.new_module("stream.ustream-hls")
+        self.poller = self.APIPoller(api)
+        self.poller.setDaemon(True)
 
     def open(self):
-        reader = UHSStreamReader(self)
+        reader = HLSStreamReader(self)
         reader.open()
-
+        self.poller.start()
+        self.logger.debug("Starting API polling thread")
         return reader
 
 
 class UStreamTV(Plugin):
+    url_re = re.compile(r"""
+    https?://(www\.)?ustream\.tv
+        (?:
+            (/embed/|/channel/id/)(?P<channel_id>\d+)
+        )?
+        (?:
+            (/embed)?/recorded/(?P<video_id>\d+)
+        )?
+    """, re.VERBOSE)
+    media_id_re = re.compile(r'"ustream:channel_id"\s+content\s*=\s*"(\d+)"')
     options = PluginOptions({
-        "password": ""
+        "password": None
     })
 
     @classmethod
     def can_handle_url(cls, url):
-        return _url_re.match(url)
-
-    @classmethod
-    def stream_weight(cls, stream):
-        match = re.match(r"mobile_(\w+)", stream)
-        if match:
-            weight, group = Plugin.stream_weight(match.group(1))
-            weight -= 1
-            group = "mobile_ustream"
-        elif stream == "recorded":
-            weight, group = 720, "ustream"
-        else:
-            weight, group = Plugin.stream_weight(stream)
-
-        return weight, group
-
-    def _get_channel_id(self):
-        res = http.get(self.url)
-        match = _channel_id_re.search(res.text)
-        if match:
-            return int(match.group(1))
-
-    def _get_hls_streams(self, channel_id, wait_for_transcode=False):
-        # HLS streams are created on demand, so we may have to wait
-        # for a transcode to be started.
-        attempts = wait_for_transcode and 10 or 1
-        playlist_url = HLS_PLAYLIST_URL.format(channel_id)
-        streams = {}
-        while attempts and not streams:
-            try:
-                streams = HLSStream.parse_variant_playlist(self.session,
-                                                           playlist_url,
-                                                           nameprefix="mobile_")
-            except IOError:
-                # Channel is probably offline
-                break
-
-            attempts -= 1
-            sleep(3)
-
-        return streams
-
-    def _create_rtmp_stream(self, cdn, stream_name):
-        parsed = urlparse(cdn)
-        params = {
-            "rtmp": cdn,
-            "app": parsed.path[1:],
-            "playpath": stream_name,
-            "pageUrl": self.url,
-            "swfUrl": SWF_URL,
-            "live": True
-        }
-
-        return RTMPStream(self.session, params)
-
-    def _get_module_info(self, app, media_id, password="", schema=None):
-        self.logger.debug("Waiting for moduleInfo invoke")
-        conn = create_ums_connection(app, media_id, self.url, password)
-
-        attempts = 3
-        while conn.connected and attempts:
-            try:
-                result = conn.process_packets(invoked_method="moduleInfo",
-                                              timeout=10)
-            except (IOError, librtmp.RTMPError) as err:
-                raise PluginError("Failed to get stream info: {0}".format(err))
-
-            try:
-                result = _module_info_schema.validate(result)
-                break
-            except PluginError:
-                attempts -= 1
-
-        conn.close()
-
-        if schema:
-            result = schema.validate(result)
-
-        return result
-
-    def _get_desktop_streams(self, channel_id):
-        password = self.options.get("password")
-        channel = self._get_module_info("channel", channel_id, password,
-                                        schema=_channel_schema)
-
-        if not isinstance(channel.get("stream"), list):
-            raise NoStreamsError(self.url)
-
-        streams = {}
-        for provider in channel["stream"]:
-            if provider["name"] == u"uhs_akamai":  # not heavily tested, but got a stream working
-                continue
-            provider_url = provider["url"]
-            provider_name = provider["name"]
-            for stream_index, stream_info in enumerate(provider["streams"]):
-                stream = None
-                stream_height = int(stream_info.get("height", 0))
-                stream_name = stream_info.get("description")
-                if not stream_name:
-                    if stream_height > 0:
-                        if not stream_info.get("isTranscoded"):
-                            stream_name = "{0}p+".format(stream_height)
-                        else:
-                            stream_name = "{0}p".format(stream_height)
+        return cls.url_re.match(url) is not None
+
+    def _api_get_streams(self, media_id, application, cluster="live", referrer=None, retries=3):
+        if retries > 0:
+            app_id = 11
+            app_ver = 2
+            referrer = referrer or self.url
+            self.api = UHSClient(self.session, media_id, application, referrer=referrer, cluster=cluster, app_id=app_id,
+                                 app_version=app_ver, password=self.get_option("password"))
+            self.logger.debug("Connecting to UStream API: media_id={0}, application={1}, referrer={2}, cluster={3}, "
+                              "app_id={4}, app_ver={5}",
+                              media_id, application, referrer, cluster, app_id, app_ver)
+            if self.api.connect():
+                for i in range(5):  # make at most five requests to get the moduleInfo
+                    try:
+                        for s in self._do_poll(media_id, application, cluster, referrer, retries):
+                            yield s
+                    except ModuleInfoNoStreams:
+                        self.logger.debug("Retrying moduleInfo request")
+                        time.sleep(1)
                     else:
-                        stream_name = "live"
-
-                if stream_name in streams:
-                    provider_name_clean = provider_name.replace("uhs_", "")
-                    stream_name += "_alt_{0}".format(provider_name_clean)
-
-                if provider_name.startswith("uhs_"):
-                    stream = UHSStream(self.session, channel_id,
-                                       self.url, provider_name,
-                                       stream_index, password)
-                elif provider_url.startswith("rtmp"):
-                    playpath = stream_info["streamName"]
-                    stream = self._create_rtmp_stream(provider_url,
-                                                      playpath)
-
-                if stream:
-                    streams[stream_name] = stream
-
-        return streams
-
-    def _get_live_streams(self, channel_id):
-        has_desktop_streams = False
-        if HAS_LIBRTMP:
-            try:
-                streams = self._get_desktop_streams(channel_id)
-                # TODO: Replace with "yield from" when dropping Python 2.
-                for stream in streams.items():
-                    has_desktop_streams = True
-                    yield stream
-            except PluginError as err:
-                self.logger.error("Unable to fetch desktop streams: {0}", err)
-            except NoStreamsError:
-                pass
-        else:
-            self.logger.warning(
-                "python-librtmp is not installed, but is needed to access "
-                "the desktop streams"
-            )
-
-        try:
-            streams = self._get_hls_streams(channel_id,
-                                            wait_for_transcode=not has_desktop_streams)
+                        break
+
+    def _do_poll(self, media_id, application, cluster="live", referrer=None, retries=3):
+        res = self.api.poll()
+        if res:
+            for result in res:
+                if result["cmd"] == "moduleInfo":
+                    for s in self.handle_module_info(result["args"], media_id, application, cluster,
+                                                     referrer, retries):
+                        yield s
+                elif result["cmd"] == "reject":
+                    for s in self.handle_reject(result["args"], media_id, application, cluster, referrer, retries):
+                        yield s
+                else:
+                    self.logger.debug("Unknown command: {0}({1})", result["cmd"], result["args"])
+
+    def handle_module_info(self, args, media_id, application, cluster="live", referrer=None, retries=3):
+        has_results = False
+        for streams in UHSClient.module_info_schema.validate(args):
+            has_results = True
+            if isinstance(streams, list):
+                for stream in streams:
+                    for q, s in HLSStream.parse_variant_playlist(self.session, stream["url"]).items():
+                        yield q, UStreamHLSStream(self.session, s.url, self.api)
+            elif isinstance(streams, dict):
+                for stream in streams.get("streams", []):
+                    name = "{0}k".format(stream["bitrate"])
+                    for surl in stream["streamName"]:
+                        yield name, HTTPStream(self.session, surl)
+            elif streams == "offline":
+                self.logger.warning("This stream is currently offline")
+
+        if not has_results:
+            raise ModuleInfoNoStreams
+
+    def handle_reject(self, args, media_id, application, cluster="live", referrer=None, retries=3):
+        for arg in args:
+            if "cluster" in arg:
+                self.logger.debug("Switching cluster to {0}", arg["cluster"]["name"])
+                cluster = arg["cluster"]["name"]
+            if "referrerLock" in arg:
+                referrer = arg["referrerLock"]["redirectUrl"]
+
+        return self._api_get_streams(media_id,
+                                     application,
+                                     cluster=cluster,
+                                     referrer=referrer,
+                                     retries=retries - 1)
 
-            # TODO: Replace with "yield from" when dropping Python 2.
-            for stream in streams.items():
-                yield stream
-        except PluginError as err:
-            self.logger.error("Unable to fetch mobile streams: {0}", err)
-        except NoStreamsError:
-            pass
-
-    def _get_recorded_streams(self, video_id):
-        if HAS_LIBRTMP:
-            recording = self._get_module_info("recorded", video_id,
-                                              schema=_recorded_schema)
-
-            if not isinstance(recording.get("stream"), list):
-                return
-
-            for provider in recording["stream"]:
-                base_url = provider.get("url")
-                for stream_info in provider["streams"]:
-                    bitrate = int(stream_info.get("bitrate", 0))
-                    stream_name = (bitrate > 0 and "{0}k".format(bitrate) or
-                                   "recorded")
-
-                    url = stream_info["streamName"]
-                    if base_url:
-                        url = base_url + url
-
-                    if url.startswith("http"):
-                        yield stream_name, HTTPStream(self.session, url)
-                    elif url.startswith("rtmp"):
-                        params = dict(rtmp=url, pageUrl=self.url)
-                        yield stream_name, RTMPStream(self.session, params)
+    def _get_streams(self):
+        # establish a mobile non-websockets api connection
+        umatch = self.url_re.match(self.url)
+        application = "channel"
 
+        channel_id = umatch.group("channel_id")
+        video_id = umatch.group("video_id")
+        if channel_id:
+            application = "channel"
+            media_id = channel_id
+        elif video_id:
+            application = "recorded"
+            media_id = video_id
         else:
-            self.logger.warning(
-                "The proper API could not be used without python-librtmp "
-                "installed. Stream URL is not guaranteed to be valid"
-            )
-
-            url = RECORDED_URL.format(video_id)
-            random_hash = "{0:02x}{1:02x}".format(randint(0, 255),
-                                                  randint(0, 255))
-            params = dict(hash=random_hash)
-            stream = HTTPStream(self.session, url, params=params)
-            yield "recorded", stream
+            media_id = self._find_media_id()
 
-    def _get_streams(self):
-        match = _url_re.match(self.url)
-
-        video_id = match.group("video_id")
-        if video_id:
-            return self._get_recorded_streams(video_id)
+        if media_id:
+            for s in self._api_get_streams(media_id, application):
+                yield s
+        else:
+            self.logger.error("Cannot find a media_id on this page")
 
-        channel_id = match.group("channel_id") or self._get_channel_id()
-        if channel_id:
-            return self._get_live_streams(channel_id)
+    def _find_media_id(self):
+        self.logger.debug("Searching for media ID on the page")
+        res = http.get(self.url, headers={"User-Agent": useragents.CHROME})
+        m = self.media_id_re.search(res.text)
+        return m and m.group(1)
 
 
 __plugin__ = UStreamTV
diff --git a/src/streamlink/stream/hds.py b/src/streamlink/stream/hds.py
index d7baf1fa..45910bd5 100644
--- a/src/streamlink/stream/hds.py
+++ b/src/streamlink/stream/hds.py
@@ -224,7 +224,6 @@ class HDSStreamWorker(SegmentedStreamWorker):
     def fragment_count(self):
         table = self.fragmentruntable.payload.fragment_run_entry_table
         first_fragment, end_fragment = None, None
-        max_end_fragment = 0
 
         for i, fragmentrun in enumerate(table):
             if fragmentrun.discontinuity_indicator is not None:
@@ -240,16 +239,11 @@ class HDSStreamWorker(SegmentedStreamWorker):
             fragment_duration = (fragmentrun.first_fragment_timestamp +
                                  fragmentrun.fragment_duration)
 
-            max_end_fragment = max(fragmentrun.first_fragment, max_end_fragment)
-
             if self.timestamp > fragment_duration:
                 offset = ((self.timestamp - fragment_duration) /
                           fragmentrun.fragment_duration)
                 end_fragment += int(offset)
 
-        # don't go past the last fragment
-        end_fragment = min(max_end_fragment, end_fragment)
-
         if first_fragment is None:
             first_fragment = 1
 


Test patch:
diff --git a/tests/test_plugin_artetv.py b/tests/test_plugin_artetv.py
index 033d1edf..1463b67d 100644
--- a/tests/test_plugin_artetv.py
+++ b/tests/test_plugin_artetv.py
@@ -6,6 +6,16 @@ from streamlink.plugins.artetv import ArteTV
 class TestPluginArteTV(unittest.TestCase):
     def test_can_handle_url(self):
         # should match
+        # new url
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/fr/direct/"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/de/live/"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/de/videos/074633-001-A/gesprach-mit-raoul-peck"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/en/videos/071437-010-A/sunday-soldiers"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/fr/videos/074633-001-A/entretien-avec-raoul-peck"))
+        self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/pl/videos/069873-000-A/supermama-i-businesswoman"))
+
+        # should match
+        # old url - some of them get redirected and some are 404
         self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/direct"))
         self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/de/live"))
         self.assertTrue(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/024031-000-A/le-testament-du-docteur-mabuse"))
@@ -17,5 +27,6 @@ class TestPluginArteTV(unittest.TestCase):
         # shouldn't match
         self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/guide/fr/plus7/"))
         self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/guide/de/plus7/"))
-        self.assertFalse(ArteTV.can_handle_url("http://www.tvcatchup.com/"))
-        self.assertFalse(ArteTV.can_handle_url("http://www.youtube.com/"))
+        # shouldn't match - playlists without video ids in url
+        self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/en/videos/RC-014457/the-power-of-forests/"))
+        self.assertFalse(ArteTV.can_handle_url("http://www.arte.tv/en/videos/RC-013118/street-art/"))
diff --git a/tests/test_plugin_canalplus.py b/tests/test_plugin_canalplus.py
index 7b841e26..ca2f499f 100644
--- a/tests/test_plugin_canalplus.py
+++ b/tests/test_plugin_canalplus.py
@@ -17,6 +17,9 @@ class TestPluginCanalPlus(unittest.TestCase):
         self.assertTrue(CanalPlus.can_handle_url("http://www.cstar.fr/emissions/pid8754-wild-transport.html"))
         self.assertTrue(CanalPlus.can_handle_url("http://www.cstar.fr/musique/pid6282-les-tops.html?vid=1430143"))
         self.assertTrue(CanalPlus.can_handle_url("http://replay.cstar.fr/video/1430245"))
+        self.assertTrue(CanalPlus.can_handle_url("http://www.cnews.fr/direct"))
+        self.assertTrue(CanalPlus.can_handle_url("http://www.cnews.fr/politique/video/des-electeurs-toujours-autant-indecis-174769"))
+        self.assertTrue(CanalPlus.can_handle_url("http://www.cnews.fr/magazines/plus-de-recul/de-recul-du-14042017-174594"))
 
         # shouldn't match
         self.assertFalse(CanalPlus.can_handle_url("http://www.canalplus.fr/"))
@@ -24,5 +27,6 @@ class TestPluginCanalPlus(unittest.TestCase):
         self.assertFalse(CanalPlus.can_handle_url("http://replay.c8.fr/"))
         self.assertFalse(CanalPlus.can_handle_url("http://www.cstar.fr/"))
         self.assertFalse(CanalPlus.can_handle_url("http://replay.cstar.fr/"))
+        self.assertFalse(CanalPlus.can_handle_url("http://www.cnews.fr/"))
         self.assertFalse(CanalPlus.can_handle_url("http://www.tvcatchup.com/"))
         self.assertFalse(CanalPlus.can_handle_url("http://www.youtube.com/"))
diff --git a/tests/test_plugin_cyro.py b/tests/test_plugin_cyro.py
new file mode 100644
index 00000000..c75d2775
--- /dev/null
+++ b/tests/test_plugin_cyro.py
@@ -0,0 +1,16 @@
+import unittest
+
+from streamlink.plugins.cyro import Cyro
+
+
+class TestPluginCyro(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Cyro.can_handle_url("http://cyro.se/watch/foobar"))
+        self.assertTrue(Cyro.can_handle_url("http://cyro.se/watch/foobar/s1/e4"))
+        self.assertTrue(Cyro.can_handle_url("https://cyro.se/watch/barfoo"))
+        self.assertTrue(Cyro.can_handle_url("https://cyro.se/watch/foobar/s1/e4"))
+
+        # shouldn't match
+        self.assertFalse(Cyro.can_handle_url("http://cyro.se"))
+        self.assertFalse(Cyro.can_handle_url("https://cyro.se"))
diff --git a/tests/test_plugin_eltrecetv.py b/tests/test_plugin_eltrecetv.py
new file mode 100644
index 00000000..7c016d6a
--- /dev/null
+++ b/tests/test_plugin_eltrecetv.py
@@ -0,0 +1,15 @@
+import unittest
+
+from streamlink.plugins.eltrecetv import ElTreceTV
+
+
+class TestPluginElTreceTV(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(ElTreceTV.can_handle_url("http://www.eltrecetv.com.ar/vivo"))
+        self.assertTrue(ElTreceTV.can_handle_url("http://www.eltrecetv.com.ar/pasapalabra/capitulo-1_084027"))
+        self.assertTrue(ElTreceTV.can_handle_url("http://www.eltrecetv.com.ar/a-todo-o-nada-2014/programa-2_072251"))
+
+        # shouldn't match
+        self.assertFalse(ElTreceTV.can_handle_url("http://eltrecetv.com.ar/"))
+        self.assertFalse(ElTreceTV.can_handle_url("https://www.youtube.com/c/eltrece"))
\ No newline at end of file
diff --git a/tests/test_plugin_facebook.py b/tests/test_plugin_facebook.py
new file mode 100644
index 00000000..b6b171a1
--- /dev/null
+++ b/tests/test_plugin_facebook.py
@@ -0,0 +1,14 @@
+import unittest
+
+from streamlink.plugins.facebook import Facebook
+
+
+class TestPluginFacebook(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/nos/videos/1725546430794241/"))
+        self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/nytfood/videos/1485091228202006/"))
+        self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/SporTurkTR/videos/798553173631138/"))
+
+        # shouldn't match
+        self.assertFalse(Facebook.can_handle_url("https://www.facebook.com"))
diff --git a/tests/test_plugin_oldlivestream.py b/tests/test_plugin_oldlivestream.py
new file mode 100644
index 00000000..454b42e3
--- /dev/null
+++ b/tests/test_plugin_oldlivestream.py
@@ -0,0 +1,18 @@
+import unittest
+
+from streamlink.plugins.oldlivestream import OldLivestream
+
+
+class TestPluginOldLivestream(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(OldLivestream.can_handle_url("https://cdn.livestream.com/embed/channel"))
+        self.assertTrue(OldLivestream.can_handle_url("https://original.livestream.com/embed/channel"))
+        self.assertTrue(OldLivestream.can_handle_url("https://original.livestream.com/channel"))
+
+        # shouldn't match
+        self.assertFalse(OldLivestream.can_handle_url("https://cdn.livestream.com"))
+        self.assertFalse(OldLivestream.can_handle_url("https://original.livestream.com"))
+        # other plugin livestream.py
+        self.assertFalse(OldLivestream.can_handle_url("https://livestream.com"))
+        self.assertFalse(OldLivestream.can_handle_url("https://www.livestream.com"))
diff --git a/tests/test_plugin_pcyourfreetv.py b/tests/test_plugin_pcyourfreetv.py
index d1ee96dc..f5664754 100644
--- a/tests/test_plugin_pcyourfreetv.py
+++ b/tests/test_plugin_pcyourfreetv.py
@@ -6,13 +6,13 @@ from streamlink.plugins.pcyourfreetv import PCYourFreeTV
 class TestPluginPCYourFreeTV(unittest.TestCase):
     def test_can_handle_url(self):
         # should match
-        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=das%20erste&page_id=41"))
-        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=srf%20eins&page_id=41"))
-        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=bbc%20one&page_id=41"))
-        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=tf1&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/indexplayer.php?channel=das%20erste&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/indexplayer.php?channel=srf%20eins&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/indexplayer.php?channel=bbc%20one&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/indexplayer.php?channel=tf1&page_id=41"))
 
         # shouldn't match
         self.assertFalse(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/home.php"))
-        self.assertFalse(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_livetv.php?page_id=1"))
+        self.assertFalse(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/indexlivetv.php?page_id=1"))
         self.assertFalse(PCYourFreeTV.can_handle_url("http://tvcatchup.com/"))
         self.assertFalse(PCYourFreeTV.can_handle_url("http://youtube.com/"))
diff --git a/tests/test_plugin_rte.py b/tests/test_plugin_rte.py
new file mode 100644
index 00000000..62114a52
--- /dev/null
+++ b/tests/test_plugin_rte.py
@@ -0,0 +1,23 @@
+import unittest
+
+from streamlink.plugins.rte import RTE
+
+
+class TestPluginRTE(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/8/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/10/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/6/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/live/7/"))
+
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/rte-news-one-oclock-30003248/10714679/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/the-ray-darcy-show-extras-30003588/10714469/"))
+        self.assertTrue(RTE.can_handle_url("http://www.rte.ie/player/99/show/lotto-1251/10714463/"))
+
+        # shouldn't match
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/"))
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/tv/"))
+        self.assertFalse(RTE.can_handle_url("http://www.rte.ie/player/"))
+        self.assertFalse(RTE.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(RTE.can_handle_url("http://www.youtube.com/"))
diff --git a/tests/test_plugin_telefe.py b/tests/test_plugin_telefe.py
new file mode 100644
index 00000000..d8ae58dc
--- /dev/null
+++ b/tests/test_plugin_telefe.py
@@ -0,0 +1,16 @@
+import unittest
+
+from streamlink.plugins.telefe import Telefe
+
+
+class TestPluginTelefe(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Telefe.can_handle_url("http://telefe.com/pone-a-francella/temporada-1/programa-01/"))
+        self.assertTrue(Telefe.can_handle_url("http://telefe.com/los-simuladores/temporada-1/capitulo-01/"))
+        self.assertTrue(Telefe.can_handle_url("http://telefe.com/dulce-amor/capitulos/capitulo-01/"))
+
+        # shouldn't match
+        self.assertFalse(Telefe.can_handle_url("http://telefe.com/"))
+        self.assertFalse(Telefe.can_handle_url("http://www.telefeinternacional.com.ar/"))
+        self.assertFalse(Telefe.can_handle_url("http://marketing.telefe.com/"))
\ No newline at end of file
diff --git a/tests/test_plugin_tv1channel.py b/tests/test_plugin_tv1channel.py
new file mode 100644
index 00000000..982c4259
--- /dev/null
+++ b/tests/test_plugin_tv1channel.py
@@ -0,0 +1,15 @@
+import unittest
+
+from streamlink.plugins.tv1channel import TV1Channel
+
+
+class TestPluginTV1Channel(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(TV1Channel.can_handle_url("http://tv1channel.org/"))
+        self.assertTrue(TV1Channel.can_handle_url("http://tv1channel.org/index.php/livetv"))
+        self.assertTrue(TV1Channel.can_handle_url("http://www.tv1channel.org/play/video.php?id=325"))
+        self.assertTrue(TV1Channel.can_handle_url("http://www.tv1channel.org/play/video.php?id=340"))
+
+        # shouldn't match
+        self.assertFalse(TV1Channel.can_handle_url("https://local.local"))
diff --git a/tests/test_plugin_tvplayer.py b/tests/test_plugin_tvplayer.py
index 13c7c363..52f27dc0 100644
--- a/tests/test_plugin_tvplayer.py
+++ b/tests/test_plugin_tvplayer.py
@@ -1,4 +1,3 @@
-import json
 import unittest
 
 from streamlink import Streamlink
@@ -31,28 +30,28 @@ class TestPluginTVPlayer(unittest.TestCase):
         self.assertFalse(TVPlayer.can_handle_url("http://www.tvcatchup.com/"))
         self.assertFalse(TVPlayer.can_handle_url("http://www.youtube.com/"))
 
+    @patch('streamlink.plugins.tvplayer.TVPlayer._get_stream_data')
     @patch('streamlink.plugins.tvplayer.http')
     @patch('streamlink.plugins.tvplayer.HLSStream')
-    def test_get_streams(self, hlsstream, mock_http):
-        api_data = {
-            "tvplayer": {
-                "status": "200 OK",
-                "response": {
+    def test_get_streams(self, hlsstream, mock_http, mock_get_stream_data):
+        mock_get_stream_data.return_value = {
                     "stream": "http://test.se/stream1"
                 }
-            }
-        }
+
         page_resp = Mock()
         page_resp.text = u"""
-        var validate = "foo";
-        var resourceId = "1234";
-        var platform = "test";
+                    <div class="video-js theoplayer-skin theo-seekbar-above-controls content-box vjs-fluid"
+                 data-resource= "89"
+                 data-token = "1324567894561268987948596154656418448489159"
+                                    data-content-type="live"
+                    data-environment="live"
+                    data-subscription="free"
+                    data-channel-id="89">
+                <div id="channel-info" class="channel-info">
+                    <div class="row visible-xs visible-sm">
         """
-        api_resp = Mock()
-        api_resp.text = json.dumps(api_data)
+
         mock_http.get.return_value = page_resp
-        mock_http.post.return_value = api_resp
-        mock_http.json.return_value = api_data["tvplayer"]["response"]
         hlsstream.parse_variant_playlist.return_value = {"test": HLSStream(self.session, "http://test.se/stream1")}
 
         plugin = TVPlayer("http://tvplayer.com/watch/dave")
@@ -64,11 +63,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_http.post.assert_called_with("http://api.tvplayer.com/api/v2/stream/live", data=dict(service=1,
-                                                                                                  id=u"1234",
-                                                                                                  validate=u"foo",
-                                                                                                  platform=u"test",
-                                                                                                  token=None))
+        mock_get_stream_data.assert_called_with(resource="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")