Repository: streamlink/streamlink

Issue:
SRG SSR plugin
Hello

Another challenge for beardypig. ;)
Swiss public TV includes channels in : 
- German : SRF 1, SRF 2, SRF Info : `http://www.srf.ch/play/tv/live`
- French : RTS Un, RTS Deux, RTS Info : `http://www.rts.ch/play/tv/direct`
- Italian : RSI La 1, RSI La 2 : `http://www.rsi.ch/play/tv/live`

Sometimes they air live sports events exclusively online on their website. 
Channels are of course geoblocked to CH but it can be resolved with proxy. 

Example address source for RTS Un can be found here :
`http://il.srgssr.ch/integrationlayer/1.0/ue/rts/video/play/3608506.json`

They propose HDS and HLS.


Discussion:
I'll take a look, if I can find a decent Swiss proxy :-)
@karlo2105 I had a go, I wasn't able to test it as I couldn't find a working Swiss proxy ... 
This is added, if you could test it @karlo2105 and confirm we can close this.
Thanks very much I will give a try and let you known if i manage with working proxy. ;) 
When I use proxy, it buffers a bit but with streamlink I got error on all channels. 
Take a look : 

`[cli][info] Found matching plugin srgssr for URL http://www.srf.ch/play/tv/live#
?tvLiveId=c4927fcf-e1a0-0001-7edd-1ef01d441651
error: Unable to open URL: http://il.srgssr.ch/integrationlayer/1.0/ue/c4927fcf-
e1a0-0001-7edd-1ef01d441651/video/play/srf.json (404 Client Error: Not Found for
 url: http://il.srgssr.ch/integrationlayer/1.0/ue/c4927fcf-e1a0-0001-7edd-1ef01d
441651/video/play/srf.json)
[End of Streamlink for Windows]`

Some additional addresses : 
----------------------------
SRF.ch
------
`http://www.srf.ch/livestream/player/srf-1`
`http://www.srf.ch/livestream/player/srf-2`
`http://www.srf.ch/livestream/player/srf-info`

Live sports events : you can find below events marked with small cam which will be broadcasted on their website
`http://www.srf.ch/sport/resultcenter/results`

RTS.ch
------
Live sports events 
`http://www.rts.ch/sport/programmes/`

RSI.ch
------
Live sports events
`http://live.rsi.ch/results.html`
Live streaming
`http://www.rsi.ch/live-streaming/`

I send you proxy on PM. 

Cheers.


Resolving patch:
diff --git a/src/streamlink/compat.py b/src/streamlink/compat.py
--- a/src/streamlink/compat.py
+++ b/src/streamlink/compat.py
@@ -21,14 +21,15 @@ def bytes(b, enc="ascii"):
 
 try:
     from urllib.parse import (
-        urlparse, urlunparse, urljoin, quote, unquote, parse_qsl
+        urlparse, urlunparse, urljoin, quote, unquote, parse_qsl, urlencode
     )
     import queue
 except ImportError:
     from urlparse import urlparse, urlunparse, urljoin, parse_qsl
-    from urllib import quote, unquote
+    from urllib import quote, unquote, urlencode
     import Queue as queue
 
+
 __all__ = ["is_py2", "is_py3", "is_py33", "is_win32", "str", "bytes",
            "urlparse", "urlunparse", "urljoin", "parse_qsl", "quote",
-           "unquote", "queue", "range"]
+           "unquote", "queue", "range", "urlencode"]
diff --git a/src/streamlink/plugins/srgssr.py b/src/streamlink/plugins/srgssr.py
--- a/src/streamlink/plugins/srgssr.py
+++ b/src/streamlink/plugins/srgssr.py
@@ -1,32 +1,46 @@
 from __future__ import print_function
+
 import re
 
+from streamlink.compat import urlparse, parse_qsl
 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.compat import urlparse, parse_qsl
 
 
 class SRGSSR(Plugin):
-    url_re = re.compile(r"https?://(?:www\.)?(srf|rts|rsi|rtr)\.ch/play/tv")
+    url_re = re.compile(r"""https?://(?:www\.)?
+            (srf|rts|rsi|rtr)\.ch/
+            (?:
+                play/tv|
+                livestream/player|
+                live-streaming|
+                sport/direct/(\d+)-
+            )""", re.VERBOSE)
     api_url = "http://il.srgssr.ch/integrationlayer/1.0/ue/{site}/video/play/{id}.json"
-    video_id_re = re.compile(r'urn:(srf|rts|rsi|rtr):(?:ais:)?video:([^&"]+)')
+    token_url = "http://tp.srgssr.ch/akahd/token"
+    video_id_re = re.compile(r'urn(?:%3A|:)(srf|rts|rsi|rtr)(?:%3A|:)(?:ais(?:%3A|:))?video(?:%3A|:)([^&"]+)')
     video_id_schema = validate.Schema(validate.transform(video_id_re.search))
     api_schema = validate.Schema(
-        {"Video":
-            {"Playlists":
-                {"Playlist": [{
-                    "@protocol": validate.text,
-                    "url": [{"@quality": validate.text, "text": validate.url()}]
-                }]
+        {
+            "Video":
+                {
+                    "Playlists":
+                        {
+                            "Playlist": [{
+                                "@protocol": validate.text,
+                                "url": [{"@quality": validate.text, "text": validate.url()}]
+                            }]
+                        }
                 }
-            }
         },
         validate.get("Video"),
         validate.get("Playlists"),
         validate.get("Playlist"))
+    token_schema = validate.Schema({"token": {"authparams": validate.text}},
+                                   validate.get("token"),
+                                   validate.get("authparams"))
 
     @classmethod
     def can_handle_url(cls, url):
@@ -37,11 +51,14 @@ def get_video_id(self):
         qinfo = dict(parse_qsl(parsed.query or parsed.fragment.lstrip("?")))
 
         site, video_id = None, None
+        url_m = self.url_re.match(self.url)
 
         # look for the video id in the URL, otherwise find it in the page
         if "tvLiveId" in qinfo:
             video_id = qinfo["tvLiveId"]
-            site = self.url_re.match(self.url).group(1)
+            site = url_m.group(1)
+        elif url_m.group(2):
+            site, video_id = url_m.group(1), url_m.group(2)
         else:
             video_id_m = http.get(self.url, schema=self.video_id_schema)
             if video_id_m:
@@ -49,8 +66,16 @@ def get_video_id(self):
 
         return site, video_id
 
+    def get_authparams(self, url):
+        parsed = urlparse(url)
+        path, _ = parsed.path.rsplit("/", 1)
+        token_res = http.get(self.token_url, params=dict(acl=path + "/*"))
+        authparams = http.json(token_res, schema=self.token_schema)
+        self.logger.debug("Found authparams: {0}", authparams)
+        return dict(parse_qsl(authparams))
+
     def _get_streams(self):
-        video_id, site = self.get_video_id()
+        site, video_id = self.get_video_id()
 
         if video_id and site:
             self.logger.debug("Found {0} video ID {1}", site, video_id)
@@ -59,11 +84,9 @@ def _get_streams(self):
 
             for stream_info in http.json(res, schema=self.api_schema):
                 for url in stream_info["url"]:
-                    if stream_info["@protocol"] == "HTTP-HDS":
-                        for s in HDSStream.parse_manifest(self.session, url["text"]).items():
-                            yield s
                     if stream_info["@protocol"] == "HTTP-HLS":
-                        for s in HLSStream.parse_variant_playlist(self.session, url["text"]).items():
+                        params = self.get_authparams(url["text"])
+                        for s in HLSStream.parse_variant_playlist(self.session, url["text"], params=params).items():
                             yield s
 
 
diff --git a/src/streamlink/plugins/swisstxt.py b/src/streamlink/plugins/swisstxt.py
new file mode 100644
--- /dev/null
+++ b/src/streamlink/plugins/swisstxt.py
@@ -0,0 +1,45 @@
+from __future__ import print_function
+
+import re
+
+from streamlink.compat import urlparse, parse_qsl, urlunparse
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+from streamlink.stream import HLSStream
+
+
+class Swisstxt(Plugin):
+    url_re = re.compile(r"""https?://(?:
+        live\.(rsi)\.ch/|
+        (?:www\.)?(srf)\.ch/sport/resultcenter
+    )""", re.VERBOSE)
+    api_url = "http://event.api.swisstxt.ch/v1/stream/{site}/byEventItemIdAndType/{id}/HLS"
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls.url_re.match(url) is not None and cls.get_event_id(url)
+
+    @classmethod
+    def get_event_id(cls, url):
+        return dict(parse_qsl(urlparse(url).query.lower())).get("eventid")
+
+    def get_stream_url(self, event_id):
+        url_m = self.url_re.match(self.url)
+        site = url_m.group(1) or url_m.group(2)
+        api_url = self.api_url.format(id=event_id, site=site.upper())
+        self.logger.debug("Calling API: {0}", api_url)
+
+        stream_url = http.get(api_url).text.strip("\"'")
+
+        parsed = urlparse(stream_url)
+        query = dict(parse_qsl(parsed.query))
+        return urlunparse(parsed._replace(query="")), query
+
+    def _get_streams(self):
+        stream_url, params = self.get_stream_url(self.get_event_id(self.url))
+        return HLSStream.parse_variant_playlist(self.session,
+                                                stream_url,
+                                                params=params)
+
+
+__plugin__ = Swisstxt


Test patch:
diff --git a/tests/test_plugin_srgssr.py b/tests/test_plugin_srgssr.py
--- a/tests/test_plugin_srgssr.py
+++ b/tests/test_plugin_srgssr.py
@@ -3,7 +3,7 @@
 from streamlink.plugins.srgssr import SRGSSR
 
 
-class TestPluginCrunchyroll(unittest.TestCase):
+class TestPluginSRGSSR(unittest.TestCase):
     def test_can_handle_url(self):
         # should match
         self.assertTrue(SRGSSR.can_handle_url("http://srf.ch/play/tv/live"))
@@ -13,6 +13,8 @@ def test_can_handle_url(self):
         self.assertTrue(SRGSSR.can_handle_url("http://rtr.ch/play/tv/live"))
         self.assertTrue(SRGSSR.can_handle_url("http://rts.ch/play/tv/direct#?tvLiveId=3608506"))
         self.assertTrue(SRGSSR.can_handle_url("http://www.srf.ch/play/tv/live#?tvLiveId=c49c1d64-9f60-0001-1c36-43c288c01a10"))
+        self.assertTrue(SRGSSR.can_handle_url("http://www.rts.ch/sport/direct/8328501-tennis-open-daustralie.html"))
+        self.assertTrue(SRGSSR.can_handle_url("http://www.rts.ch/play/tv/tennis/video/tennis-open-daustralie?id=8328501"))
 
         # shouldn't match
         self.assertFalse(SRGSSR.can_handle_url("http://www.crunchyroll.com/gintama"))
diff --git a/tests/test_plugin_swisstxt.py b/tests/test_plugin_swisstxt.py
new file mode 100644
--- /dev/null
+++ b/tests/test_plugin_swisstxt.py
@@ -0,0 +1,28 @@
+import unittest
+
+from streamlink.plugins.swisstxt import Swisstxt
+
+
+class TestPluginSwisstxt(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Swisstxt.can_handle_url("http://www.srf.ch/sport/resultcenter/tennis?eventId=338052"))
+        self.assertTrue(Swisstxt.can_handle_url("http://live.rsi.ch/tennis.html?eventId=338052"))
+        self.assertTrue(Swisstxt.can_handle_url("http://live.rsi.ch/sport.html?eventId=12345"))
+
+        # shouldn't match
+        # regular srgssr sites
+        self.assertFalse(Swisstxt.can_handle_url("http://srf.ch/play/tv/live"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.rsi.ch/play/tv/live#?tvLiveId=livestream_La1"))
+        self.assertFalse(Swisstxt.can_handle_url("http://rsi.ch/play/tv/live?tvLiveId=livestream_La1"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.rtr.ch/play/tv/live"))
+        self.assertFalse(Swisstxt.can_handle_url("http://rtr.ch/play/tv/live"))
+        self.assertFalse(Swisstxt.can_handle_url("http://rts.ch/play/tv/direct#?tvLiveId=3608506"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.srf.ch/play/tv/live#?tvLiveId=c49c1d64-9f60-0001-1c36-43c288c01a10"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.rts.ch/sport/direct/8328501-tennis-open-daustralie.html"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.rts.ch/play/tv/tennis/video/tennis-open-daustralie?id=8328501"))
+
+        # other sites
+        self.assertFalse(Swisstxt.can_handle_url("http://www.crunchyroll.com/gintama"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.crunchyroll.es/gintama"))
+        self.assertFalse(Swisstxt.can_handle_url("http://www.youtube.com/"))