Repository: streamlink/streamlink

Issue:
tvcatchup plugin broken
tvcatchup.com/watch/dave
they encoded their source with base64

```
function b64DecodeUnicode(str) {
  // Going backwards: from bytestream, to percent-encoding, to original string.
  return decodeURIComponent(atob(str).split('').map(function(c) {
    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));
}
var a7b5b2a9d1f72b8d2202fc725e95a9b58 = "aHR0cHM6Ly9zMTQ3LmNkbi50dmNhdGNodXAuY29tL3BzaGlmdF9zZXNzX2VjMGNkNTBhODM1ODkvZGF2ZV9hZHAubTN1OD9jbGllbnRLZXk9MTk5MzhiODkxYTRmN2UzZjZlNTNjMWU1ZTFmZjhhYmMtTVRVd056QTJNRE0wT0E9PSZkZXZpY2U9d2ViJmNoYW5uZWxpZD0yMCZjaGFubmVsPTIwJmVhPVRXOTZhV3hzWVM4MUxqQWdLRmRw";

jwplayer('playerrpKPIEPXXyLT').setup({
  file: b64DecodeUnicode(a7b5b2a9d1f72b8d2202fc725e95a9b58),
  width: '100%',
  //height: '480',
  aspectratio: '16:9',
  autostart: 'true',
  stretching: 'exactfit',
  cast: {}
});
```

Discussion:

Patch:
diff --git a/docs/ext_argparse.py b/docs/ext_argparse.py
index 06b8cae8..e95b9ab4 100644
--- a/docs/ext_argparse.py
+++ b/docs/ext_argparse.py
@@ -17,10 +17,10 @@ from collections import namedtuple
 from textwrap import dedent
 
 from docutils import nodes
-from docutils.parsers.rst import Directive
 from docutils.parsers.rst.directives import unchanged
 from docutils.statemachine import ViewList
 from sphinx.util.nodes import nested_parse_with_titles
+from sphinx.util.compat import Directive
 
 
 _ArgumentParser = argparse.ArgumentParser
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index db98da77..44fe7537 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -81,7 +81,8 @@ dogan               - teve2.com.tr       Yes   Yes   VOD is supported for teve2
                     - dreamtv.com.tr
                     - cnnturk.com
                     - dreamturk.com.tr
-dogus               - ntvspor.net        Yes   No
+dogus               - startv.com.tr      Yes   No
+                    - ntvspor.net
                     - kralmuzik.com.tr
                     - ntv.com.tr
                     - eurostartv.com.tr
@@ -187,7 +188,6 @@ srgssr              - srf.ch             Yes   No    Streams are geo-restricted
                     - rsi.ch
                     - rtr.ch
 ssh101              ssh101.com           Yes   No
-startv              startv.com.tr        Yes   No
 streamable          streamable.com       -     Yes
 streamboat          streamboat.tv        Yes   No
 streamingvi... [1]_ streamingvid... [2]_ Yes   --    RTMP streams requires rtmpdump with
diff --git a/src/streamlink/plugins/canlitv.py b/src/streamlink/plugins/canlitv.py
index 7c56102a..64c73fa3 100644
--- a/src/streamlink/plugins/canlitv.py
+++ b/src/streamlink/plugins/canlitv.py
@@ -5,14 +5,14 @@ from streamlink.plugin.api import http
 from streamlink.plugin.api import useragents
 from streamlink.stream import HLSStream
 
-EMBED_URL_1 = "http://www.canlitv.plus/kanallar.php?kanal={0}"
+EMBED_URL_1 = "http://www.canlitv.life/kanallar.php?kanal={0}"
 EMBED_URL_2 = "http://www.ecanlitvizle.net/embed.php?kanal={0}"
 
 _m3u8_re = re.compile(r"""file\s*:\s*['"](?P<url>[^"']+)['"]""")
 _url_re = re.compile(r"""http(s)?://(?:www\.)?(?P<domain>
-    canlitv\.(com|plus)
+    canlitv\.(com|life)
     |
-    canlitvlive\.(io|co|live|site)
+    canlitvlive\.(co|live)
     |
     ecanlitvizle\.net
     )
@@ -35,7 +35,7 @@ class Canlitv(Plugin):
             "User-Agent": useragents.FIREFOX
         }
 
-        if domain == "canlitv.plus":
+        if domain == "canlitv.life":
             res = http.get(EMBED_URL_1.format(channel), headers=headers)
         elif domain == "ecanlitvizle.net":
             res = http.get(EMBED_URL_2.format(channel), headers=headers)
@@ -47,9 +47,6 @@ class Canlitv(Plugin):
         if url_match:
             hls_url = url_match.group("url")
 
-            if domain in ("canlitvlive.live", "canlitvlive.site"):
-                 hls_url = "http:" + hls_url
-
             self.logger.debug("Found URL: {0}".format(hls_url))
 
             try:
diff --git a/src/streamlink/plugins/cinergroup.py b/src/streamlink/plugins/cinergroup.py
index 32625ca4..0e588f0a 100644
--- a/src/streamlink/plugins/cinergroup.py
+++ b/src/streamlink/plugins/cinergroup.py
@@ -16,7 +16,7 @@ class CinerGroup(Plugin):
     """
     url_re = re.compile(r"""https?://(?:www.)?
         (?:
-            showtv.com.tr/canli-yayin(/showtv)?|
+            showtv.com.tr/canli-yayin/showtv|
             haberturk.com/canliyayin|
             showmax.com.tr/canliyayin|
             showturk.com.tr/canli-yayin/showturk|
diff --git a/src/streamlink/plugins/dogan.py b/src/streamlink/plugins/dogan.py
index 90c70ea2..9732e984 100644
--- a/src/streamlink/plugins/dogan.py
+++ b/src/streamlink/plugins/dogan.py
@@ -24,7 +24,7 @@ class Dogan(Plugin):
     """, re.VERBOSE)
     playerctrl_re = re.compile(r'''<div[^>]*?ng-controller=(?P<quote>["'])(?:Live)?PlayerCtrl(?P=quote).*?>''', re.DOTALL)
     data_id_re = re.compile(r'''data-id=(?P<quote>["'])(?P<id>\w+)(?P=quote)''')
-    content_id_re = re.compile(r'"content(?:I|i)d", "(\w+)"')
+    content_id_re = re.compile(r'"contentId", "(\w+)"')
     content_api = "/actions/content/media/{id}"
     new_content_api = "/action/media/{id}"
     content_api_schema = validate.Schema({
@@ -32,7 +32,7 @@ class Dogan(Plugin):
         "Media": {
             "Link": {
                 "DefaultServiceUrl": validate.url(),
-                validate.optional("ServiceUrl"): validate.any(validate.url(), ""),
+                validate.optional("ServiceUrl"): validate.url(),
                 "SecurePath": validate.text,
             }
         }
@@ -44,11 +44,6 @@ class Dogan(Plugin):
 
     def _get_content_id(self):
         res = http.get(self.url)
-        # find the contentId
-        content_id_m = self.content_id_re.search(res.text)
-        if content_id_m:
-            return content_id_m.group(1)
-
         # find the PlayerCtrl div
         player_ctrl_m = self.playerctrl_re.search(res.text)
         if player_ctrl_m:
@@ -58,6 +53,10 @@ class Dogan(Plugin):
             if content_id_m:
                 return content_id_m.group("id")
 
+        # use the fall back regex
+        content_id_m = self.content_id_re.search(res.text)
+        return content_id_m and content_id_m.group(1)
+
     def _get_hls_url(self, content_id):
         # make the api url relative to the current domain
         if "cnnturk" in self.url or "teve2.com.tr" in self.url:
diff --git a/src/streamlink/plugins/dogus.py b/src/streamlink/plugins/dogus.py
index 4e1f17e1..4d58d33b 100644
--- a/src/streamlink/plugins/dogus.py
+++ b/src/streamlink/plugins/dogus.py
@@ -1,18 +1,23 @@
+from __future__ import print_function
+
 import re
 
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http
+from streamlink.plugin.api import validate
+from streamlink.stream import HDSStream
 from streamlink.stream import HLSStream
 from streamlink.utils import update_scheme
 
 
 class Dogus(Plugin):
     """
-    Support for live streams from Dogus sites include ntv, ntvspor, and kralmuzik
+    Support for live streams from Dogus sites include startv, ntv, ntvspor, and kralmuzik
     """
 
     url_re = re.compile(r"""https?://(?:www.)?
         (?:
+            startv.com.tr/canli-yayin|
             ntv.com.tr/canli-yayin/ntv|
             ntvspor.net/canli-yayin|
             kralmuzik.com.tr/tv/kral-tv|
@@ -21,27 +26,61 @@ class Dogus(Plugin):
         )/?""", re.VERBOSE)
     mobile_url_re = re.compile(r"""(?P<q>["'])(?P<url>(https?:)?//[^'"]*?/live/hls/[^'"]*?\?token=)
                                    (?P<token>[^'"]*?)(?P=q)""", re.VERBOSE)
+    desktop_url_re = re.compile(r"""(?P<q>["'])(?P<url>(https?:)?//[^'"]*?/live/hds/[^'"]*?\?token=)
+                                    (?P<token>[^'"]*?)(?P=q)""", re.VERBOSE)
     token_re = re.compile(r"""token=(?P<q>["'])(?P<token>[^'"]*?)(?P=q)""")
 
+    hds_schema = validate.Schema(validate.all(
+        {
+            "success": True,
+            "xtra": {
+                "url": validate.url(),
+                "use_akamai": bool
+            }
+        },
+        validate.get("xtra")
+    ))
+    SWF_URL = "http://dygassets.akamaized.net/player2/plugins/flowplayer/flowplayer.httpstreaming-3.2.11.swf"
+
     @classmethod
     def can_handle_url(cls, url):
         return cls.url_re.match(url) is not None
 
+    def _get_star_streams(self, desktop_url, mobile_url, token=""):
+        if token:
+            self.logger.debug("Opening stream with token: {}", token)
+
+        if mobile_url:
+            for _, s in HLSStream.parse_variant_playlist(self.session,
+                                                         mobile_url + token,
+                                                         headers={"Referer": self.url}).items():
+                yield "live", s
+        if desktop_url:
+            # get the HDS stream URL
+            res = http.get(desktop_url + token)
+            stream_data = http.json(res, schema=self.hds_schema)
+            for _, s in HDSStream.parse_manifest(self.session,
+                                                 stream_data["url"],
+                                                 pvswf=self.SWF_URL,
+                                                 is_akamai=stream_data["use_akamai"],
+                                                 headers={"Referer": self.url}).items():
+                yield "live", s
+
     def _get_streams(self):
         res = http.get(self.url)
         mobile_url_m = self.mobile_url_re.search(res.text)
+        desktop_url_m = self.desktop_url_re.search(res.text)
 
-        mobile_url = mobile_url_m and update_scheme(self.url, mobile_url_m.group("url"))
+        desktop_url = desktop_url_m and  update_scheme(self.url, desktop_url_m.group("url"))
+        mobile_url = mobile_url_m and  update_scheme(self.url, mobile_url_m.group("url"))
 
-        token = mobile_url_m and mobile_url_m.group("token")
+        token = (desktop_url_m and desktop_url_m.group("token")) or (mobile_url_m and mobile_url_m.group("token"))
         if not token:
             # if no token is in the url, try to find it else where in the page
             token_m = self.token_re.search(res.text)
             token = token_m and token_m.group("token")
 
-        return HLSStream.parse_variant_playlist(self.session, mobile_url + token,
-                                                headers={"Referer": self.url})
-
+        return self._get_star_streams(desktop_url, mobile_url, token=token)
 
 
 __plugin__ = Dogus
diff --git a/src/streamlink/plugins/huya.py b/src/streamlink/plugins/huya.py
index 65aa5cda..8c45ac97 100644
--- a/src/streamlink/plugins/huya.py
+++ b/src/streamlink/plugins/huya.py
@@ -10,7 +10,7 @@ from streamlink.plugin.api import useragents
 HUYA_URL = "http://m.huya.com/%s"
 
 _url_re = re.compile(r'http(s)?://(www\.)?huya.com/(?P<channel>[^/]+)', re.VERBOSE)
-_hls_re = re.compile(r'^\s*<video\s+id="html5player-video"\s+src="(?P<url>[^"]+)"', re.MULTILINE)
+_hls_re = re.compile(r'^\s*<source\s+src="(?P<url>[^"]+)"/>', re.MULTILINE)
 
 _hls_schema = validate.Schema(
         validate.all(
diff --git a/src/streamlink/plugins/liveme.py b/src/streamlink/plugins/liveme.py
index f7a83b88..b96a52bc 100644
--- a/src/streamlink/plugins/liveme.py
+++ b/src/streamlink/plugins/liveme.py
@@ -1,4 +1,4 @@
-import random
+from __future__ import print_function
 import re
 
 from streamlink.plugin import Plugin
@@ -11,7 +11,7 @@ from streamlink.stream import HTTPStream
 
 class LiveMe(Plugin):
     url_re = re.compile(r"https?://(www.)?liveme\.com/live\.html\?videoid=(\d+)")
-    api_url = "https://live.ksmobile.net/live/queryinfo"
+    api_url = "http://live.ksmobile.net/live/queryinfo?userid=1&videoid={id}"
     api_schema = validate.Schema(validate.all({
         "status": "200",
         "data": {
@@ -26,9 +26,6 @@ class LiveMe(Plugin):
     def can_handle_url(cls, url):
         return cls.url_re.match(url) is not None
 
-    def _random_t(self, t):
-        return "".join(random.choice("ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678") for _ in range(t))
-
     def _make_stream(self, url):
         if url and url.endswith("flv"):
             return HTTPStream(self.session, url)
@@ -40,16 +37,8 @@ class LiveMe(Plugin):
         video_id = url_params.get("videoid")
 
         if video_id:
-            vali = '{0}l{1}m{2}'.format(self._random_t(4), self._random_t(4), self._random_t(5))
-            data = {
-                'userid': 1,
-                'videoid': video_id,
-                'area': '',
-                'h5': 1,
-                'vali': vali
-            }
-            self.logger.debug("Found Video ID: {0}".format(video_id))
-            res = http.post(self.api_url, data=data)
+            self.logger.debug("Found Video ID: {}", video_id)
+            res = http.get(self.api_url.format(id=video_id))
             data = http.json(res, schema=self.api_schema)
             hls = self._make_stream(data["video_info"]["hlsvideosource"])
             video = self._make_stream(data["video_info"]["videosource"])
diff --git a/src/streamlink/plugins/pandatv.py b/src/streamlink/plugins/pandatv.py
index ab8de87d..324b1e76 100644
--- a/src/streamlink/plugins/pandatv.py
+++ b/src/streamlink/plugins/pandatv.py
@@ -1,27 +1,24 @@
+"""Plugin for panda.tv by Fat Deer"""
+
 import re
 import types
 import time
 import json
 
-from streamlink.compat import quote
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate
 from streamlink.stream import HTTPStream
 
-ROOM_API = "https://www.panda.tv/api_room_v3?token=&hostid={0}&roomid={1}&roomkey={2}&_={3}&param={4}&time={5}&sign={6}"
-ROOM_API_V2 = "https://www.panda.tv/api_room_v2?roomid={0}&_={1}"
-SD_URL_PATTERN = "https://pl{0}.live.panda.tv/live_panda/{1}.flv?sign={2}&ts={3}&rid={4}"
-HD_URL_PATTERN = "https://pl{0}.live.panda.tv/live_panda/{1}_mid.flv?sign={2}&ts={3}&rid={4}"
-OD_URL_PATTERN = "https://pl{0}.live.panda.tv/live_panda/{1}_small.flv?sign={2}&ts={3}&rid={4}"
+ROOM_API = "http://www.panda.tv/api_room_v3?roomid={0}&roomkey={1}&_={2}"
+ROOM_API_V2 = "http://www.panda.tv/api_room_v2?roomid={0}&_={1}"
+SD_URL_PATTERN = "http://pl{0}.live.panda.tv/live_panda/{1}.flv?sign={2}&ts={3}&rid={4}"
+HD_URL_PATTERN = "http://pl{0}.live.panda.tv/live_panda/{1}_mid.flv?sign={2}&ts={3}&rid={4}"
+OD_URL_PATTERN = "http://pl{0}.live.panda.tv/live_panda/{1}_small.flv?sign={2}&ts={3}&rid={4}"
 
 _url_re = re.compile(r"http(s)?://(\w+.)?panda.tv/(?P<channel>[^/&?]+)")
 _room_id_re = re.compile(r'data-room-id="(\d+)"')
 _status_re = re.compile(r'"status"\s*:\s*"(\d+)"\s*,\s*"display_type"')
 _room_key_re = re.compile(r'"room_key"\s*:\s*"(.+?)"')
-_hostid_re = re.compile(r'\\"hostid\\"\s*:\s*\\"(.+?)\\"')
-_param_re = re.compile(r'"param"\s*:\s*"(.+?)"\s*,\s*"time"')
-_time_re = re.compile(r'"time"\s*:\s*(\d+)')
-_sign_re = re.compile(r'"sign"\s*:\s*"(.+?)"')
 _sd_re = re.compile(r'"SD"\s*:\s*"(\d+)"')
 _hd_re = re.compile(r'"HD"\s*:\s*"(\d+)"')
 _od_re = re.compile(r'"OD"\s*:\s*"(\d+)"')
@@ -47,7 +44,7 @@ _room_schema = validate.Schema(
 
 class Pandatv(Plugin):
     @classmethod
-    def can_handle_url(cls, url):
+    def can_handle_url(self, url):
         return _url_re.match(url)
 
     def _get_streams(self):
@@ -67,10 +64,6 @@ class Pandatv(Plugin):
         try:
             status = _status_re.search(res.text).group(1)
             room_key = _room_key_re.search(res.text).group(1)
-            hostid = _hostid_re.search(res.text).group(1)
-            param = _param_re.search(res.text).group(1)
-            tt = _time_re.search(res.text).group(1)
-            sign = _sign_re.search(res.text).group(1)
             sd = _sd_re.search(res.text).group(1)
             hd = _hd_re.search(res.text).group(1)
             od = _od_re.search(res.text).group(1)
@@ -83,9 +76,7 @@ class Pandatv(Plugin):
             return
 
         ts = int(time.time())
-        param = param.replace("\\", "")
-        param = quote(param)
-        url = ROOM_API.format(hostid, channel, room_key, ts, param, tt, sign)
+        url = ROOM_API.format(channel, room_key, ts)
         room = http.get(url)
         data = http.json(room, schema=_room_schema)
         if not isinstance(data, dict):
@@ -104,24 +95,17 @@ class Pandatv(Plugin):
             self.logger.info("Please Check PandaTV Room API")
             return
 
+        plflag0 = plflag.split('_')[1]
+        if plflag0 != '3':
+            plflag1 = '4'
+        else:
+            plflag1 = '3'
+
         plflag_list = json.loads(plflag_list)
-        backup = plflag_list["backup"]
         rid = plflag_list["auth"]["rid"]
         sign = plflag_list["auth"]["sign"]
         ts = plflag_list["auth"]["time"]
 
-        backup.append(plflag)
-        plflag0 = backup
-        plflag0 = [i.split('_')[1] for i in plflag0] 
-
-        # let wangsu cdn priority, flag can see here in "H5PLAYER_CDN_LINES":
-        # https://www.panda.tv/cmstatic/global-config.js
-        lines = ["3", "4"]
-        try:
-            plflag1 = [i for i in plflag0 if i in lines][0]
-        except IndexError:
-            plflag1 = plflag0[0]
-
         if sd == '1':
             streams['ehq'] = HTTPStream(self.session, SD_URL_PATTERN.format(plflag1, room_key, sign, ts, rid))
 
diff --git a/src/streamlink/plugins/startv.py b/src/streamlink/plugins/startv.py
deleted file mode 100644
index b136c6b0..00000000
--- a/src/streamlink/plugins/startv.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from __future__ import print_function
-import re
-
-from streamlink import streams
-from streamlink.plugin import Plugin
-from streamlink.plugin.api import http
-from streamlink.plugin.api import validate
-
-
-class StarTV(Plugin):
-    url_re = re.compile(r"https?://(?:www\.)?startv.com.tr/canli-yayin")
-    iframe_re = re.compile(r'frame .*?src="(https://www.youtube.com/[^"]+)"')
-
-    @classmethod
-    def can_handle_url(cls, url):
-        return cls.url_re.match(url) is not None
-
-    def _get_streams(self):
-        res = http.get(self.url)
-        m = self.iframe_re.search(res.text)
-
-        yt_url = m and m.group(1)
-        if yt_url:
-            self.logger.debug("Deferring to YouTube plugin with URL: {0}".format(yt_url))
-            return streams(yt_url)
-
-
-__plugin__ = StarTV
diff --git a/src/streamlink/plugins/telefe.py b/src/streamlink/plugins/telefe.py
index 93d9165b..fd19b63a 100644
--- a/src/streamlink/plugins/telefe.py
+++ b/src/streamlink/plugins/telefe.py
@@ -45,4 +45,4 @@ class Telefe(Plugin):
         if video_url_found_http:
             yield "http", HTTPStream(self.session, video_url_found_http)
 
-__plugin__ = Telefe
+__plugin__ = Telefe
\ No newline at end of file
diff --git a/src/streamlink/plugins/turkuvaz.py b/src/streamlink/plugins/turkuvaz.py
index 3d54adfd..8a14ebbe 100644
--- a/src/streamlink/plugins/turkuvaz.py
+++ b/src/streamlink/plugins/turkuvaz.py
@@ -2,7 +2,6 @@ import random
 import re
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http
-from streamlink.plugin.api import useragents
 from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream
 
@@ -13,10 +12,10 @@ class Turkuvaz(Plugin):
     """
 
     _url_re = re.compile(r"""https?://(?:www.)?
-                             (?:(atvavrupa).tv|
-                                (atv|a2tv|ahaber|aspor|minikago|minikacocuk|anews).com.tr)
-                                /webtv/(live-broadcast|canli-yayin)""",
-                         re.VERBOSE)
+            (?:
+                (atv|a2tv|ahaber|aspor|minikago|minikacocuk).com.tr/webtv/canli-yayin|
+                (atvavrupa).tv/webtv/videoizle/atv_avrupa/canli_yayin
+            )""", re.VERBOSE)
     _hls_url = "http://trkvz-live.ercdn.net/{channel}/{channel}.m3u8"
     _token_url = "http://videotoken.tmgrup.com.tr/webtv/secure"
     _token_schema = validate.Schema(validate.all(
@@ -37,19 +36,15 @@ class Turkuvaz(Plugin):
         # remap the domain to channel
         channel = {"atv": "atvhd",
                    "ahaber": "ahaberhd",
-                   "aspor": "asporhd",
-                   "anews": "anewshd",
-                   "minikacocuk": "minikagococuk"}.get(domain, domain)
+                   "aspor": "asporhd"}.get(domain, domain)
+
         hls_url = self._hls_url.format(channel=channel)
         # get the secure HLS URL
         res = http.get(self._token_url,
-                       params="url={0}".format(hls_url),
-                       headers={"Referer": self.url,
-                                "User-Agent": useragents.CHROME})
-
+                       params={"url": hls_url},
+                       headers={"Referer": self.url})
         secure_hls_url = http.json(res, schema=self._token_schema)
 
-        self.logger.debug("Found HLS URL: {0}".format(secure_hls_url))
         return HLSStream.parse_variant_playlist(self.session, secure_hls_url)
 
 
diff --git a/src/streamlink/plugins/tvcatchup.py b/src/streamlink/plugins/tvcatchup.py
index 25018fdf..ca6a60d2 100644
--- a/src/streamlink/plugins/tvcatchup.py
+++ b/src/streamlink/plugins/tvcatchup.py
@@ -6,7 +6,7 @@ from streamlink.stream import HLSStream
 
 USER_AGENT = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
 _url_re = re.compile(r"http://(?:www\.)?tvcatchup.com/watch/\w+")
-_stream_re = re.compile(r'''(?P<q>["'])(?P<stream_url>https?://.*m3u8\?.*clientKey=.*?)(?P=q)''')
+_stream_re = re.compile(r'''source.*?(?P<q>["'])(?P<stream_url>https?://.*m3u8\?.*clientKey=.*?)(?P=q)''')
 
 
 class TVCatchup(Plugin):
diff --git a/src/streamlink/plugins/webcast_india_gov.py b/src/streamlink/plugins/webcast_india_gov.py
index 40b988cf..4069fb09 100644
--- a/src/streamlink/plugins/webcast_india_gov.py
+++ b/src/streamlink/plugins/webcast_india_gov.py
@@ -26,4 +26,4 @@ class WebcastIndiaGov(Plugin):
         except:
             self.logger.error("The requested channel is unavailable.")
 
-__plugin__ = WebcastIndiaGov
+__plugin__ = WebcastIndiaGov
\ No newline at end of file
diff --git a/src/streamlink/plugins/zattoo.py b/src/streamlink/plugins/zattoo.py
index 37e6e2bd..966500eb 100644
--- a/src/streamlink/plugins/zattoo.py
+++ b/src/streamlink/plugins/zattoo.py
@@ -57,7 +57,7 @@ class Zattoo(Plugin):
         self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='zattoo:attributes')
         self._authed = self._session_attributes.get('beaker.session.id') and self._session_attributes.get('pzuid') and self._session_attributes.get('power_guide_hash')
         self._uuid = self._session_attributes.get('uuid')
-        self._expires = self._session_attributes.get('expires', 946684800)
+        self._expires = self._session_attributes.get('expires')
 
         self.base_url = 'https://{0}'.format(Zattoo._url_re.match(url).group('base_url'))
         self.headers = {


Test patch:
diff --git a/tests/test_plugin_canlitv.py b/tests/test_plugin_canlitv.py
index 58041011..0c5c69c4 100644
--- a/tests/test_plugin_canlitv.py
+++ b/tests/test_plugin_canlitv.py
@@ -20,22 +20,13 @@ class TestPluginCanlitv(unittest.TestCase):
 
     def test_can_handle_url(self):
         # should match
-        self.assertTrue(Canlitv.can_handle_url("http://www.canlitv.plus/channel"))
         self.assertTrue(Canlitv.can_handle_url("http://www.canlitv.com/channel"))
+        self.assertTrue(Canlitv.can_handle_url("http://www.canlitv.life/channel"))
         self.assertTrue(Canlitv.can_handle_url("http://www.canlitvlive.co/izle/channel.html"))
         self.assertTrue(Canlitv.can_handle_url("http://www.canlitvlive.live/izle/channel.html"))
-        self.assertTrue(Canlitv.can_handle_url("http://www.canlitvlive.io/izle/channel.html"))
-        self.assertTrue(Canlitv.can_handle_url("http://www.canlitvlive.site/izle/channel.html"))
         self.assertTrue(Canlitv.can_handle_url("http://www.ecanlitvizle.net/channel/"))
         self.assertTrue(Canlitv.can_handle_url("http://www.ecanlitvizle.net/onizleme.php?kanal=channel"))
         self.assertTrue(Canlitv.can_handle_url("http://www.ecanlitvizle.net/tv.php?kanal=channel"))
         # shouldn't match
         self.assertFalse(Canlitv.can_handle_url("http://www.canlitv.com"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.canlitv.plus"))
         self.assertFalse(Canlitv.can_handle_url("http://www.ecanlitvizle.net"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.canlitvlive.co"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.canlitvlive.live"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.canlitvlive.io"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.canlitvlive.site"))
-        self.assertFalse(Canlitv.can_handle_url("http://www.ecanlitvizle.net"))
-
diff --git a/tests/test_plugin_eltrecetv.py b/tests/test_plugin_eltrecetv.py
index 83485bca..7c016d6a 100644
--- a/tests/test_plugin_eltrecetv.py
+++ b/tests/test_plugin_eltrecetv.py
@@ -12,4 +12,4 @@ class TestPluginElTreceTV(unittest.TestCase):
 
         # 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"))
+        self.assertFalse(ElTreceTV.can_handle_url("https://www.youtube.com/c/eltrece"))
\ No newline at end of file
diff --git a/tests/test_plugin_telefe.py b/tests/test_plugin_telefe.py
index 5d1d8492..d8ae58dc 100644
--- a/tests/test_plugin_telefe.py
+++ b/tests/test_plugin_telefe.py
@@ -13,4 +13,4 @@ class TestPluginTelefe(unittest.TestCase):
         # 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/"))
+        self.assertFalse(Telefe.can_handle_url("http://marketing.telefe.com/"))
\ No newline at end of file
diff --git a/tests/test_webcast_india_gov.py b/tests/test_webcast_india_gov.py
index 3cc4b500..1790c127 100644
--- a/tests/test_webcast_india_gov.py
+++ b/tests/test_webcast_india_gov.py
@@ -13,4 +13,4 @@ class TestPluginWebcastIndiaGov(unittest.TestCase):
         # shouldn't match
         self.assertFalse(WebcastIndiaGov.can_handle_url("http://meity.gov.in/"))
         self.assertFalse(WebcastIndiaGov.can_handle_url("http://www.nic.in/"))
-        self.assertFalse(WebcastIndiaGov.can_handle_url("http://digitalindiaawards.gov.in/"))
+        self.assertFalse(WebcastIndiaGov.can_handle_url("http://digitalindiaawards.gov.in/"))
\ No newline at end of file