Repository: streamlink/streamlink

Issue:
RSI.ch new API 2.0
## Plugin Issue
- [x] This is a plugin issue and I have read the contribution guidelines.

### Description

RSI.ch now uses a new API to download the Json file containing the streaming information. So the current plugin no longer works. Obviously with Swiss IP.

### Explicit stream URLs to test (Only from Swiss IP)

Live Streaming: https://www.rsi.ch/play/tv/live?tvLiveId=livestream_La1
JSON: https://il.srgssr.ch/integrationlayer/2.0/mediaComposition/byUrn/urn:rsi:video:livestream_La1.json?onlyChapters=true&vector=portalplay
Streaming Data: https://rsila1-euwe.akamaized.net/333fb374-92b8-4a1d-90af-4529f56d683e/rsi1.ism/manifest(format=m3u8-aapl,encryption=cbcs-aapl,filter=nodvr)

### Log output

$ streamlink https://www.rsi.ch/play/tv/live?tvLiveId=livestream_La1 best

[cli][info] Found matching plugin srgssr for URL https://www.rsi.ch/play/tv/live?tvLiveId=livestream_La1

error: Unable to open URL: https://srgssruni12ch-lh.akamaihd.net/i/enc12uni_ch@191527/master.m3u8?hdnts=exp%3D1556873673%7Eacl%3D%2Fi%2Fenc12uni_ch%40191527%2F%2A%7Ehmac%3D82d1c28280c7ad5fe12618106b6600e1593c2acd90bd2db4b9df05a6dbd36f0c (400 Client Error: Bad Request for url: https://srgssruni12ch-lh.akamaihd.net/i/enc12uni_ch@191527/master.m3u8?hdnts=exp%3D1556873673~acl%3D%2Fi%2Fenc12uni_ch%40191527%2F%2A~hmac%3D82d1c28280c7ad5fe12618106b6600e1593c2acd90bd2db4b9df05a6dbd36f0c)


Discussion:
I rewrote the _srgssr.py_ plugin adapting it to the new API, it acquires the new url correctly. 

But now I have this error:
**[stream.hls] [error] Failed to create decryptor: ('Unable to decrypt cipher {0}', 'SAMPLE-AES')**

I do not understand if it is possible to overcome this coding or not, can you help me?

New plugin conde:

```
from __future__ import print_function

import re

from streamlink import PluginError
from streamlink.compat import urlparse, parse_qsl, urlencode, urlunparse
from streamlink.plugin import Plugin
from streamlink.plugin.api import validate
from streamlink.stream import HLSStream


class SRGSSR(Plugin):
    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 = "https://il.srgssr.ch/integrationlayer/2.0/mediaComposition/byUrn/urn:{site}:video:{id}.json"
    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()}]
                            }]
                        }
                }
        },
        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):
        return cls.url_re.match(url) is not None

    def get_video_id(self):
        parsed = urlparse(self.url)
        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 = url_m.group(1)
        elif url_m.group(2):
            site, video_id = url_m.group(1), url_m.group(2)
        else:
            video_id_m = self.session.http.get(self.url, schema=self.video_id_schema)
            if video_id_m:
                site, video_id = video_id_m.groups()
        return site, video_id

    def auth_url(self, url):
        parsed = urlparse(url)
        path, _ = parsed.path.rsplit("/", 1)
        token_res = self.session.http.get(self.token_url, params=dict(acl=path + "/*"))
        authparams = self.session.http.json(token_res, schema=self.token_schema)

        existing = dict(parse_qsl(parsed.query))
        existing.update(dict(parse_qsl(authparams)))

        return urlunparse(parsed._replace(query=urlencode(existing)))

    def _get_streams(self):
        site, video_id = self.get_video_id()

        if video_id and site:
            self.logger.debug("Found {0} video ID {1}", site, video_id)

            try:
                res = self.session.http.get(self.api_url.format(site=site, id=video_id))
            except PluginError:
                return

            data = self.session.http.json(res)
            
            for each in data["chapterList"][0]["resourceList"]:
                if each["protocol"] == "HLS":
                    for s in HLSStream.parse_variant_playlist(self.session, self.auth_url(each["url"])).items():
                        yield s
            

__plugin__ = SRGSSR
```

Ok, I just saw that in issues #2155 @back-to said that this type of DRM is not supported. :(

I leave you the decision, or not, to import the new code.
Output with debug loglevel:

> $ streamlink --plugin-dirs="./personalplugin/"  https://www.rsi.ch/play/tv/live?tvLiveId=livestream_La1 best --loglevel debug
> [session][debug] Plugin srgssr is being overridden by /personalplugin/srgssr.py
> [cli][debug] OS:         macOS 10.14.4
> [cli][debug] Python:     3.6.5
> [cli][debug] Streamlink: 1.1.1
> [cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.48.0)
> [cli][info] Found matching plugin srgssr for URL https://www.rsi.ch/play/tv/live?tvLiveId=livestream_La1
> [plugin.srgssr][debug] Found rsi video ID livestream_La1
> [utils.l10n][debug] Language code: it_IT
> [cli][info] Available streams: 138k (worst), 180p, 272p, 360p, 544p (best)
> [cli][info] Opening stream: 544p (hls)
> [stream.hls][debug] Reloading playlist
> [stream.hls][debug] Segments in this playlist are encrypted
> [stream.hls][debug] First Sequence: 1025469; Last Sequence: 1025477
> [stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 1025475; End Sequence: None
> [stream.hls][debug] Adding segment 1025475 to queue
> [cli][debug] Pre-buffering 8192 bytes
> [stream.hls][debug] Adding segment 1025476 to queue
> [stream.hls][debug] Adding segment 1025477 to queue
> [stream.hls][error] Failed to create decryptor: ('Unable to decrypt cipher {0}', 'SAMPLE-AES')
> [stream.segmented][debug] Closing writer thread
> [stream.segmented][debug] Closing worker thread
> [cli][error] Try 1/1: Could not open stream <HLSStream('https://rsila1-euwe.akamaized.net/333fb374-92b8-4a1d-90af-4529f56d683e/rsi1.ism/QualityLevels(1999600)/Manifest(video,format=m3u8-aapl,filter=nodvr)')> (No data returned from stream)
> error: Could not open stream <HLSStream('https://rsila1-euwe.akamaized.net/333fb374-92b8-4a1d-90af-4529f56d683e/rsi1.ism/QualityLevels(1999600)/Manifest(video,format=m3u8-aapl,filter=nodvr)')>, tried 1 times, exiting
> [cli][info] Closing currently open stream...

Resolving patch:
diff --git a/src/streamlink/plugins/srgssr.py b/src/streamlink/plugins/srgssr.py
deleted file mode 100644
--- a/src/streamlink/plugins/srgssr.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from __future__ import print_function
-
-import re
-
-from streamlink import PluginError
-from streamlink.compat import urlparse, parse_qsl, urlencode, urlunparse
-from streamlink.plugin import Plugin
-from streamlink.plugin.api import validate
-from streamlink.stream import HLSStream
-
-
-class SRGSSR(Plugin):
-    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"
-    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()}]
-                            }]
-                        }
-                }
-        },
-        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):
-        return cls.url_re.match(url) is not None
-
-    def get_video_id(self):
-        parsed = urlparse(self.url)
-        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 = url_m.group(1)
-        elif url_m.group(2):
-            site, video_id = url_m.group(1), url_m.group(2)
-        else:
-            video_id_m = self.session.http.get(self.url, schema=self.video_id_schema)
-            if video_id_m:
-                site, video_id = video_id_m.groups()
-
-        return site, video_id
-
-    def auth_url(self, url):
-        parsed = urlparse(url)
-        path, _ = parsed.path.rsplit("/", 1)
-        token_res = self.session.http.get(self.token_url, params=dict(acl=path + "/*"))
-        authparams = self.session.http.json(token_res, schema=self.token_schema)
-
-        existing = dict(parse_qsl(parsed.query))
-        existing.update(dict(parse_qsl(authparams)))
-
-        return urlunparse(parsed._replace(query=urlencode(existing)))
-
-    def _get_streams(self):
-        site, video_id = self.get_video_id()
-
-        if video_id and site:
-            self.logger.debug("Found {0} video ID {1}", site, video_id)
-
-            try:
-                res = self.session.http.get(self.api_url.format(site=site, id=video_id))
-            except PluginError:
-                return
-
-            for stream_info in self.session.http.json(res, schema=self.api_schema):
-                for url in stream_info["url"]:
-                    if stream_info["@protocol"] == "HTTP-HLS":
-                        for s in HLSStream.parse_variant_playlist(self.session, self.auth_url(url["text"])).items():
-                            yield s
-
-
-__plugin__ = SRGSSR


Test patch:
diff --git a/tests/plugins/test_srgssr.py b/tests/plugins/test_srgssr.py
deleted file mode 100644
--- a/tests/plugins/test_srgssr.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import unittest
-
-from streamlink.plugins.srgssr import SRGSSR
-
-
-class TestPluginSRGSSR(unittest.TestCase):
-    def test_can_handle_url(self):
-        # should match
-        self.assertTrue(SRGSSR.can_handle_url("http://srf.ch/play/tv/live"))
-        self.assertTrue(SRGSSR.can_handle_url("http://www.rsi.ch/play/tv/live#?tvLiveId=livestream_La1"))
-        self.assertTrue(SRGSSR.can_handle_url("http://rsi.ch/play/tv/live?tvLiveId=livestream_La1"))
-        self.assertTrue(SRGSSR.can_handle_url("http://www.rtr.ch/play/tv/live"))
-        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"))
-        self.assertFalse(SRGSSR.can_handle_url("http://www.crunchyroll.es/gintama"))
-        self.assertFalse(SRGSSR.can_handle_url("http://www.youtube.com/"))