We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BBC Links stopped working?
Anyone else BBC links stopped working?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/streamlink/plugins/bbciplayer.py`
Content:
```
1 from __future__ import print_function
2 
3 import base64
4 import logging
5 import re
6 from collections import defaultdict
7 from hashlib import sha1
8 
9 from streamlink import PluginError
10 from streamlink.compat import parse_qsl, urlparse
11 from streamlink.plugin import Plugin, PluginArguments, PluginArgument
12 from streamlink.plugin.api import validate
13 from streamlink.stream import HDSStream
14 from streamlink.stream import HLSStream
15 from streamlink.stream.dash import DASHStream
16 from streamlink.utils import parse_json
17 
18 log = logging.getLogger(__name__)
19 
20 
21 class BBCiPlayer(Plugin):
22     """
23     Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from
24     bbc.co.uk/iplayer/episode/*
25     """
26     url_re = re.compile(r"""https?://(?:www\.)?bbc.co.uk/iplayer/
27         (
28             episode/(?P<episode_id>\w+)|
29             live/(?P<channel_name>\w+)
30         )
31     """, re.VERBOSE)
32     mediator_re = re.compile(
33         r'window\.__IPLAYER_REDUX_STATE__\s*=\s*({.*?});', re.DOTALL)
34     tvip_re = re.compile(r'channel"\s*:\s*{\s*"id"\s*:\s*"(\w+?)"')
35     tvip_master_re = re.compile(r'event_master_brand=(\w+?)&')
36     account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*({.*?});')
37     swf_url = "http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf"
38     hash = base64.b64decode(
39         b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==")
40     api_url = ("http://open.live.bbc.co.uk/mediaselector/6/select/"
41                "version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/")
42     platforms = ("pc", "iptv-all")
43     session_url = "https://session.bbc.com/session"
44     auth_url = "https://account.bbc.com/signin"
45 
46     mediator_schema = validate.Schema(
47         {
48             "versions": [{"id": validate.text}]
49         },
50         validate.get("versions"), validate.get(0),
51         validate.get("id")
52     )
53     mediaselector_schema = validate.Schema(
54         validate.transform(parse_json),
55         {"media": [
56             {"connection":
57                 validate.all([{
58                     validate.optional("href"): validate.url(),
59                     validate.optional("transferFormat"): validate.text
60                 }], validate.filter(lambda c: c.get("href"))),
61                 "kind": validate.text}
62         ]},
63         validate.get("media"),
64         validate.filter(lambda x: x["kind"] == "video")
65     )
66     arguments = PluginArguments(
67         PluginArgument(
68             "username",
69             requires=["password"],
70             metavar="USERNAME",
71             help="The username used to register with bbc.co.uk."
72         ),
73         PluginArgument(
74             "password",
75             sensitive=True,
76             metavar="PASSWORD",
77             help="A bbc.co.uk account password to use with --bbciplayer-username.",
78             prompt="Enter bbc.co.uk account password"
79         ),
80         PluginArgument(
81             "hd",
82             action="store_true",
83             help="""
84             Prefer HD streams over local SD streams, some live programmes may
85             not be broadcast in HD.
86             """
87         ),
88     )
89 
90     @classmethod
91     def can_handle_url(cls, url):
92         """ Confirm plugin can handle URL """
93         return cls.url_re.match(url) is not None
94 
95     @classmethod
96     def _hash_vpid(cls, vpid):
97         return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest()
98 
99     @classmethod
100     def _extract_nonce(cls, http_result):
101         """
102         Given an HTTP response from the sessino endpoint, extract the nonce, so we can "sign" requests with it.
103         We don't really sign the requests in the traditional sense of a nonce, we just incude them in the auth requests.
104 
105         :param http_result: HTTP response from the bbc session endpoint.
106         :type http_result: requests.Response
107         :return: nonce to "sign" url requests with
108         :rtype: string
109         """
110 
111         # Extract the redirect URL from the last call
112         last_redirect_url = urlparse(http_result.history[-1].request.url)
113         last_redirect_query = dict(parse_qsl(last_redirect_url.query))
114         # Extract the nonce from the query string in the redirect URL
115         final_url = urlparse(last_redirect_query['goto'])
116         goto_url = dict(parse_qsl(final_url.query))
117         goto_url_query = parse_json(goto_url['state'])
118 
119         # Return the nonce we can use for future queries
120         return goto_url_query['nonce']
121 
122     def find_vpid(self, url, res=None):
123         """
124         Find the Video Packet ID in the HTML for the provided URL
125 
126         :param url: URL to download, if res is not provided.
127         :param res: Provide a cached version of the HTTP response to search
128         :type url: string
129         :type res: requests.Response
130         :return: Video Packet ID for a Programme in iPlayer
131         :rtype: string
132         """
133         log.debug("Looking for vpid on {0}", url)
134         # Use pre-fetched page if available
135         res = res or self.session.http.get(url)
136         m = self.mediator_re.search(res.text)
137         vpid = m and parse_json(m.group(1), schema=self.mediator_schema)
138         return vpid
139 
140     def find_tvip(self, url, master=False):
141         log.debug("Looking for {0} tvip on {1}", "master" if master else "", url)
142         res = self.session.http.get(url)
143         if master:
144             m = self.tvip_master_re.search(res.text)
145         else:
146             m = self.tvip_re.search(res.text)
147         return m and m.group(1)
148 
149     def mediaselector(self, vpid):
150         urls = defaultdict(set)
151         for platform in self.platforms:
152             url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid),
153                                       platform=platform)
154             log.debug("Info API request: {0}", url)
155             medias = self.session.http.get(url, schema=self.mediaselector_schema)
156             for media in medias:
157                 for connection in media["connection"]:
158                     urls[connection.get("transferFormat")].add(connection["href"])
159 
160         for stream_type, urls in urls.items():
161             log.debug("{0} {1} streams", len(urls), stream_type)
162             for url in list(urls):
163                 try:
164                     if stream_type == "hds":
165                         for s in HDSStream.parse_manifest(self.session,
166                                                           url).items():
167                             yield s
168                     if stream_type == "hls":
169                         for s in HLSStream.parse_variant_playlist(self.session,
170                                                                   url).items():
171                             yield s
172                     if stream_type == "dash":
173                         for s in DASHStream.parse_manifest(self.session,
174                                                            url).items():
175                             yield s
176                     log.debug("  OK:   {0}", url)
177                 except:
178                     log.debug("  FAIL: {0}", url)
179 
180     def login(self, ptrt_url):
181         """
182         Create session using BBC ID. See https://www.bbc.co.uk/usingthebbc/account/
183 
184         :param ptrt_url: The snapback URL to redirect to after successful authentication
185         :type ptrt_url: string
186         :return: Whether authentication was successful
187         :rtype: bool
188         """
189         def auth_check(res):
190             return ptrt_url in ([h.url for h in res.history] + [res.url])
191 
192         # make the session request to get the correct cookies
193         session_res = self.session.http.get(
194             self.session_url,
195             params=dict(ptrt=ptrt_url)
196         )
197 
198         if auth_check(session_res):
199             log.debug("Already authenticated, skipping authentication")
200             return True
201 
202         http_nonce = self._extract_nonce(session_res)
203         res = self.session.http.post(
204             self.auth_url,
205             params=dict(
206                 ptrt=ptrt_url,
207                 nonce=http_nonce
208             ),
209             data=dict(
210                 jsEnabled=True,
211                 username=self.get_option("username"),
212                 password=self.get_option('password'),
213                 attempts=0
214             ),
215             headers={"Referer": self.url})
216 
217         return auth_check(res)
218 
219     def _get_streams(self):
220         if not self.get_option("username"):
221             log.error(
222                 "BBC iPlayer requires an account you must login using "
223                 "--bbciplayer-username and --bbciplayer-password")
224             return
225         log.info(
226             "A TV License is required to watch BBC iPlayer streams, see the BBC website for more "
227             "information: https://www.bbc.co.uk/iplayer/help/tvlicence")
228         if not self.login(self.url):
229             log.error(
230                 "Could not authenticate, check your username and password")
231             return
232 
233         m = self.url_re.match(self.url)
234         episode_id = m.group("episode_id")
235         channel_name = m.group("channel_name")
236 
237         if episode_id:
238             log.debug("Loading streams for episode: {0}", episode_id)
239             vpid = self.find_vpid(self.url)
240             if vpid:
241                 log.debug("Found VPID: {0}", vpid)
242                 for s in self.mediaselector(vpid):
243                     yield s
244             else:
245                 log.error("Could not find VPID for episode {0}",
246                           episode_id)
247         elif channel_name:
248             log.debug("Loading stream for live channel: {0}", channel_name)
249             if self.get_option("hd"):
250                 tvip = self.find_tvip(self.url, master=True) + "_hd"
251                 if tvip:
252                     log.debug("Trying HD stream {0}...", tvip)
253                     try:
254                         for s in self.mediaselector(tvip):
255                             yield s
256                     except PluginError:
257                         log.error(
258                             "Failed to get HD streams, falling back to SD")
259                     else:
260                         return
261             tvip = self.find_tvip(self.url)
262             if tvip:
263                 log.debug("Found TVIP: {0}", tvip)
264                 for s in self.mediaselector(tvip):
265                     yield s
266 
267 
268 __plugin__ = BBCiPlayer
269
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/streamlink/plugins/bbciplayer.py b/src/streamlink/plugins/bbciplayer.py
--- a/src/streamlink/plugins/bbciplayer.py
+++ b/src/streamlink/plugins/bbciplayer.py
@@ -34,11 +34,8 @@
     tvip_re = re.compile(r'channel"\s*:\s*{\s*"id"\s*:\s*"(\w+?)"')
     tvip_master_re = re.compile(r'event_master_brand=(\w+?)&')
     account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*({.*?});')
-    swf_url = "http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf"
-    hash = base64.b64decode(
-        b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==")
-    api_url = ("http://open.live.bbc.co.uk/mediaselector/6/select/"
-               "version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/")
+    hash = base64.b64decode(b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==")
+    api_url = "https://open.live.bbc.co.uk/mediaselector/6/select/version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/"
     platforms = ("pc", "iptv-all")
     session_url = "https://session.bbc.com/session"
     auth_url = "https://account.bbc.com/signin"

{"golden_diff": "diff --git a/src/streamlink/plugins/bbciplayer.py b/src/streamlink/plugins/bbciplayer.py\n--- a/src/streamlink/plugins/bbciplayer.py\n+++ b/src/streamlink/plugins/bbciplayer.py\n@@ -34,11 +34,8 @@\n     tvip_re = re.compile(r'channel\"\\s*:\\s*{\\s*\"id\"\\s*:\\s*\"(\\w+?)\"')\n     tvip_master_re = re.compile(r'event_master_brand=(\\w+?)&')\n     account_locals_re = re.compile(r'window.bbcAccount.locals\\s*=\\s*({.*?});')\n-    swf_url = \"http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf\"\n-    hash = base64.b64decode(\n-        b\"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==\")\n-    api_url = (\"http://open.live.bbc.co.uk/mediaselector/6/select/\"\n-               \"version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/\")\n+    hash = base64.b64decode(b\"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==\")\n+    api_url = \"https://open.live.bbc.co.uk/mediaselector/6/select/version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/\"\n     platforms = (\"pc\", \"iptv-all\")\n     session_url = \"https://session.bbc.com/session\"\n     auth_url = \"https://account.bbc.com/signin\"\n", "issue": "BBC Links stopped working?\nAnyone else BBC links stopped working?\n", "before_files": [{"content": "from __future__ import print_function\n\nimport base64\nimport logging\nimport re\nfrom collections import defaultdict\nfrom hashlib import sha1\n\nfrom streamlink import PluginError\nfrom streamlink.compat import parse_qsl, urlparse\nfrom streamlink.plugin import Plugin, PluginArguments, PluginArgument\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream import HDSStream\nfrom streamlink.stream import HLSStream\nfrom streamlink.stream.dash import DASHStream\nfrom streamlink.utils import parse_json\n\nlog = logging.getLogger(__name__)\n\n\nclass BBCiPlayer(Plugin):\n    \"\"\"\n    Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from\n    bbc.co.uk/iplayer/episode/*\n    \"\"\"\n    url_re = re.compile(r\"\"\"https?://(?:www\\.)?bbc.co.uk/iplayer/\n        (\n            episode/(?P<episode_id>\\w+)|\n            live/(?P<channel_name>\\w+)\n        )\n    \"\"\", re.VERBOSE)\n    mediator_re = re.compile(\n        r'window\\.__IPLAYER_REDUX_STATE__\\s*=\\s*({.*?});', re.DOTALL)\n    tvip_re = re.compile(r'channel\"\\s*:\\s*{\\s*\"id\"\\s*:\\s*\"(\\w+?)\"')\n    tvip_master_re = re.compile(r'event_master_brand=(\\w+?)&')\n    account_locals_re = re.compile(r'window.bbcAccount.locals\\s*=\\s*({.*?});')\n    swf_url = \"http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf\"\n    hash = base64.b64decode(\n        b\"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==\")\n    api_url = (\"http://open.live.bbc.co.uk/mediaselector/6/select/\"\n               \"version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/\")\n    platforms = (\"pc\", \"iptv-all\")\n    session_url = \"https://session.bbc.com/session\"\n    auth_url = \"https://account.bbc.com/signin\"\n\n    mediator_schema = validate.Schema(\n        {\n            \"versions\": [{\"id\": validate.text}]\n        },\n        validate.get(\"versions\"), validate.get(0),\n        validate.get(\"id\")\n    )\n    mediaselector_schema = validate.Schema(\n        validate.transform(parse_json),\n        {\"media\": [\n            {\"connection\":\n                validate.all([{\n                    validate.optional(\"href\"): validate.url(),\n                    validate.optional(\"transferFormat\"): validate.text\n                }], validate.filter(lambda c: c.get(\"href\"))),\n                \"kind\": validate.text}\n        ]},\n        validate.get(\"media\"),\n        validate.filter(lambda x: x[\"kind\"] == \"video\")\n    )\n    arguments = PluginArguments(\n        PluginArgument(\n            \"username\",\n            requires=[\"password\"],\n            metavar=\"USERNAME\",\n            help=\"The username used to register with bbc.co.uk.\"\n        ),\n        PluginArgument(\n            \"password\",\n            sensitive=True,\n            metavar=\"PASSWORD\",\n            help=\"A bbc.co.uk account password to use with --bbciplayer-username.\",\n            prompt=\"Enter bbc.co.uk account password\"\n        ),\n        PluginArgument(\n            \"hd\",\n            action=\"store_true\",\n            help=\"\"\"\n            Prefer HD streams over local SD streams, some live programmes may\n            not be broadcast in HD.\n            \"\"\"\n        ),\n    )\n\n    @classmethod\n    def can_handle_url(cls, url):\n        \"\"\" Confirm plugin can handle URL \"\"\"\n        return cls.url_re.match(url) is not None\n\n    @classmethod\n    def _hash_vpid(cls, vpid):\n        return sha1(cls.hash + str(vpid).encode(\"utf8\")).hexdigest()\n\n    @classmethod\n    def _extract_nonce(cls, http_result):\n        \"\"\"\n        Given an HTTP response from the sessino endpoint, extract the nonce, so we can \"sign\" requests with it.\n        We don't really sign the requests in the traditional sense of a nonce, we just incude them in the auth requests.\n\n        :param http_result: HTTP response from the bbc session endpoint.\n        :type http_result: requests.Response\n        :return: nonce to \"sign\" url requests with\n        :rtype: string\n        \"\"\"\n\n        # Extract the redirect URL from the last call\n        last_redirect_url = urlparse(http_result.history[-1].request.url)\n        last_redirect_query = dict(parse_qsl(last_redirect_url.query))\n        # Extract the nonce from the query string in the redirect URL\n        final_url = urlparse(last_redirect_query['goto'])\n        goto_url = dict(parse_qsl(final_url.query))\n        goto_url_query = parse_json(goto_url['state'])\n\n        # Return the nonce we can use for future queries\n        return goto_url_query['nonce']\n\n    def find_vpid(self, url, res=None):\n        \"\"\"\n        Find the Video Packet ID in the HTML for the provided URL\n\n        :param url: URL to download, if res is not provided.\n        :param res: Provide a cached version of the HTTP response to search\n        :type url: string\n        :type res: requests.Response\n        :return: Video Packet ID for a Programme in iPlayer\n        :rtype: string\n        \"\"\"\n        log.debug(\"Looking for vpid on {0}\", url)\n        # Use pre-fetched page if available\n        res = res or self.session.http.get(url)\n        m = self.mediator_re.search(res.text)\n        vpid = m and parse_json(m.group(1), schema=self.mediator_schema)\n        return vpid\n\n    def find_tvip(self, url, master=False):\n        log.debug(\"Looking for {0} tvip on {1}\", \"master\" if master else \"\", url)\n        res = self.session.http.get(url)\n        if master:\n            m = self.tvip_master_re.search(res.text)\n        else:\n            m = self.tvip_re.search(res.text)\n        return m and m.group(1)\n\n    def mediaselector(self, vpid):\n        urls = defaultdict(set)\n        for platform in self.platforms:\n            url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid),\n                                      platform=platform)\n            log.debug(\"Info API request: {0}\", url)\n            medias = self.session.http.get(url, schema=self.mediaselector_schema)\n            for media in medias:\n                for connection in media[\"connection\"]:\n                    urls[connection.get(\"transferFormat\")].add(connection[\"href\"])\n\n        for stream_type, urls in urls.items():\n            log.debug(\"{0} {1} streams\", len(urls), stream_type)\n            for url in list(urls):\n                try:\n                    if stream_type == \"hds\":\n                        for s in HDSStream.parse_manifest(self.session,\n                                                          url).items():\n                            yield s\n                    if stream_type == \"hls\":\n                        for s in HLSStream.parse_variant_playlist(self.session,\n                                                                  url).items():\n                            yield s\n                    if stream_type == \"dash\":\n                        for s in DASHStream.parse_manifest(self.session,\n                                                           url).items():\n                            yield s\n                    log.debug(\"  OK:   {0}\", url)\n                except:\n                    log.debug(\"  FAIL: {0}\", url)\n\n    def login(self, ptrt_url):\n        \"\"\"\n        Create session using BBC ID. See https://www.bbc.co.uk/usingthebbc/account/\n\n        :param ptrt_url: The snapback URL to redirect to after successful authentication\n        :type ptrt_url: string\n        :return: Whether authentication was successful\n        :rtype: bool\n        \"\"\"\n        def auth_check(res):\n            return ptrt_url in ([h.url for h in res.history] + [res.url])\n\n        # make the session request to get the correct cookies\n        session_res = self.session.http.get(\n            self.session_url,\n            params=dict(ptrt=ptrt_url)\n        )\n\n        if auth_check(session_res):\n            log.debug(\"Already authenticated, skipping authentication\")\n            return True\n\n        http_nonce = self._extract_nonce(session_res)\n        res = self.session.http.post(\n            self.auth_url,\n            params=dict(\n                ptrt=ptrt_url,\n                nonce=http_nonce\n            ),\n            data=dict(\n                jsEnabled=True,\n                username=self.get_option(\"username\"),\n                password=self.get_option('password'),\n                attempts=0\n            ),\n            headers={\"Referer\": self.url})\n\n        return auth_check(res)\n\n    def _get_streams(self):\n        if not self.get_option(\"username\"):\n            log.error(\n                \"BBC iPlayer requires an account you must login using \"\n                \"--bbciplayer-username and --bbciplayer-password\")\n            return\n        log.info(\n            \"A TV License is required to watch BBC iPlayer streams, see the BBC website for more \"\n            \"information: https://www.bbc.co.uk/iplayer/help/tvlicence\")\n        if not self.login(self.url):\n            log.error(\n                \"Could not authenticate, check your username and password\")\n            return\n\n        m = self.url_re.match(self.url)\n        episode_id = m.group(\"episode_id\")\n        channel_name = m.group(\"channel_name\")\n\n        if episode_id:\n            log.debug(\"Loading streams for episode: {0}\", episode_id)\n            vpid = self.find_vpid(self.url)\n            if vpid:\n                log.debug(\"Found VPID: {0}\", vpid)\n                for s in self.mediaselector(vpid):\n                    yield s\n            else:\n                log.error(\"Could not find VPID for episode {0}\",\n                          episode_id)\n        elif channel_name:\n            log.debug(\"Loading stream for live channel: {0}\", channel_name)\n            if self.get_option(\"hd\"):\n                tvip = self.find_tvip(self.url, master=True) + \"_hd\"\n                if tvip:\n                    log.debug(\"Trying HD stream {0}...\", tvip)\n                    try:\n                        for s in self.mediaselector(tvip):\n                            yield s\n                    except PluginError:\n                        log.error(\n                            \"Failed to get HD streams, falling back to SD\")\n                    else:\n                        return\n            tvip = self.find_tvip(self.url)\n            if tvip:\n                log.debug(\"Found TVIP: {0}\", tvip)\n                for s in self.mediaselector(tvip):\n                    yield s\n\n\n__plugin__ = BBCiPlayer\n", "path": "src/streamlink/plugins/bbciplayer.py"}], "after_files": [{"content": "from __future__ import print_function\n\nimport base64\nimport logging\nimport re\nfrom collections import defaultdict\nfrom hashlib import sha1\n\nfrom streamlink import PluginError\nfrom streamlink.compat import parse_qsl, urlparse\nfrom streamlink.plugin import Plugin, PluginArguments, PluginArgument\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream import HDSStream\nfrom streamlink.stream import HLSStream\nfrom streamlink.stream.dash import DASHStream\nfrom streamlink.utils import parse_json\n\nlog = logging.getLogger(__name__)\n\n\nclass BBCiPlayer(Plugin):\n    \"\"\"\n    Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from\n    bbc.co.uk/iplayer/episode/*\n    \"\"\"\n    url_re = re.compile(r\"\"\"https?://(?:www\\.)?bbc.co.uk/iplayer/\n        (\n            episode/(?P<episode_id>\\w+)|\n            live/(?P<channel_name>\\w+)\n        )\n    \"\"\", re.VERBOSE)\n    mediator_re = re.compile(\n        r'window\\.__IPLAYER_REDUX_STATE__\\s*=\\s*({.*?});', re.DOTALL)\n    tvip_re = re.compile(r'channel\"\\s*:\\s*{\\s*\"id\"\\s*:\\s*\"(\\w+?)\"')\n    tvip_master_re = re.compile(r'event_master_brand=(\\w+?)&')\n    account_locals_re = re.compile(r'window.bbcAccount.locals\\s*=\\s*({.*?});')\n    hash = base64.b64decode(b\"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==\")\n    api_url = \"https://open.live.bbc.co.uk/mediaselector/6/select/version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/\"\n    platforms = (\"pc\", \"iptv-all\")\n    session_url = \"https://session.bbc.com/session\"\n    auth_url = \"https://account.bbc.com/signin\"\n\n    mediator_schema = validate.Schema(\n        {\n            \"versions\": [{\"id\": validate.text}]\n        },\n        validate.get(\"versions\"), validate.get(0),\n        validate.get(\"id\")\n    )\n    mediaselector_schema = validate.Schema(\n        validate.transform(parse_json),\n        {\"media\": [\n            {\"connection\":\n                validate.all([{\n                    validate.optional(\"href\"): validate.url(),\n                    validate.optional(\"transferFormat\"): validate.text\n                }], validate.filter(lambda c: c.get(\"href\"))),\n                \"kind\": validate.text}\n        ]},\n        validate.get(\"media\"),\n        validate.filter(lambda x: x[\"kind\"] == \"video\")\n    )\n    arguments = PluginArguments(\n        PluginArgument(\n            \"username\",\n            requires=[\"password\"],\n            metavar=\"USERNAME\",\n            help=\"The username used to register with bbc.co.uk.\"\n        ),\n        PluginArgument(\n            \"password\",\n            sensitive=True,\n            metavar=\"PASSWORD\",\n            help=\"A bbc.co.uk account password to use with --bbciplayer-username.\",\n            prompt=\"Enter bbc.co.uk account password\"\n        ),\n        PluginArgument(\n            \"hd\",\n            action=\"store_true\",\n            help=\"\"\"\n            Prefer HD streams over local SD streams, some live programmes may\n            not be broadcast in HD.\n            \"\"\"\n        ),\n    )\n\n    @classmethod\n    def can_handle_url(cls, url):\n        \"\"\" Confirm plugin can handle URL \"\"\"\n        return cls.url_re.match(url) is not None\n\n    @classmethod\n    def _hash_vpid(cls, vpid):\n        return sha1(cls.hash + str(vpid).encode(\"utf8\")).hexdigest()\n\n    @classmethod\n    def _extract_nonce(cls, http_result):\n        \"\"\"\n        Given an HTTP response from the sessino endpoint, extract the nonce, so we can \"sign\" requests with it.\n        We don't really sign the requests in the traditional sense of a nonce, we just incude them in the auth requests.\n\n        :param http_result: HTTP response from the bbc session endpoint.\n        :type http_result: requests.Response\n        :return: nonce to \"sign\" url requests with\n        :rtype: string\n        \"\"\"\n\n        # Extract the redirect URL from the last call\n        last_redirect_url = urlparse(http_result.history[-1].request.url)\n        last_redirect_query = dict(parse_qsl(last_redirect_url.query))\n        # Extract the nonce from the query string in the redirect URL\n        final_url = urlparse(last_redirect_query['goto'])\n        goto_url = dict(parse_qsl(final_url.query))\n        goto_url_query = parse_json(goto_url['state'])\n\n        # Return the nonce we can use for future queries\n        return goto_url_query['nonce']\n\n    def find_vpid(self, url, res=None):\n        \"\"\"\n        Find the Video Packet ID in the HTML for the provided URL\n\n        :param url: URL to download, if res is not provided.\n        :param res: Provide a cached version of the HTTP response to search\n        :type url: string\n        :type res: requests.Response\n        :return: Video Packet ID for a Programme in iPlayer\n        :rtype: string\n        \"\"\"\n        log.debug(\"Looking for vpid on {0}\", url)\n        # Use pre-fetched page if available\n        res = res or self.session.http.get(url)\n        m = self.mediator_re.search(res.text)\n        vpid = m and parse_json(m.group(1), schema=self.mediator_schema)\n        return vpid\n\n    def find_tvip(self, url, master=False):\n        log.debug(\"Looking for {0} tvip on {1}\", \"master\" if master else \"\", url)\n        res = self.session.http.get(url)\n        if master:\n            m = self.tvip_master_re.search(res.text)\n        else:\n            m = self.tvip_re.search(res.text)\n        return m and m.group(1)\n\n    def mediaselector(self, vpid):\n        urls = defaultdict(set)\n        for platform in self.platforms:\n            url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid),\n                                      platform=platform)\n            log.debug(\"Info API request: {0}\", url)\n            medias = self.session.http.get(url, schema=self.mediaselector_schema)\n            for media in medias:\n                for connection in media[\"connection\"]:\n                    urls[connection.get(\"transferFormat\")].add(connection[\"href\"])\n\n        for stream_type, urls in urls.items():\n            log.debug(\"{0} {1} streams\", len(urls), stream_type)\n            for url in list(urls):\n                try:\n                    if stream_type == \"hds\":\n                        for s in HDSStream.parse_manifest(self.session,\n                                                          url).items():\n                            yield s\n                    if stream_type == \"hls\":\n                        for s in HLSStream.parse_variant_playlist(self.session,\n                                                                  url).items():\n                            yield s\n                    if stream_type == \"dash\":\n                        for s in DASHStream.parse_manifest(self.session,\n                                                           url).items():\n                            yield s\n                    log.debug(\"  OK:   {0}\", url)\n                except:\n                    log.debug(\"  FAIL: {0}\", url)\n\n    def login(self, ptrt_url):\n        \"\"\"\n        Create session using BBC ID. See https://www.bbc.co.uk/usingthebbc/account/\n\n        :param ptrt_url: The snapback URL to redirect to after successful authentication\n        :type ptrt_url: string\n        :return: Whether authentication was successful\n        :rtype: bool\n        \"\"\"\n        def auth_check(res):\n            return ptrt_url in ([h.url for h in res.history] + [res.url])\n\n        # make the session request to get the correct cookies\n        session_res = self.session.http.get(\n            self.session_url,\n            params=dict(ptrt=ptrt_url)\n        )\n\n        if auth_check(session_res):\n            log.debug(\"Already authenticated, skipping authentication\")\n            return True\n\n        http_nonce = self._extract_nonce(session_res)\n        res = self.session.http.post(\n            self.auth_url,\n            params=dict(\n                ptrt=ptrt_url,\n                nonce=http_nonce\n            ),\n            data=dict(\n                jsEnabled=True,\n                username=self.get_option(\"username\"),\n                password=self.get_option('password'),\n                attempts=0\n            ),\n            headers={\"Referer\": self.url})\n\n        return auth_check(res)\n\n    def _get_streams(self):\n        if not self.get_option(\"username\"):\n            log.error(\n                \"BBC iPlayer requires an account you must login using \"\n                \"--bbciplayer-username and --bbciplayer-password\")\n            return\n        log.info(\n            \"A TV License is required to watch BBC iPlayer streams, see the BBC website for more \"\n            \"information: https://www.bbc.co.uk/iplayer/help/tvlicence\")\n        if not self.login(self.url):\n            log.error(\n                \"Could not authenticate, check your username and password\")\n            return\n\n        m = self.url_re.match(self.url)\n        episode_id = m.group(\"episode_id\")\n        channel_name = m.group(\"channel_name\")\n\n        if episode_id:\n            log.debug(\"Loading streams for episode: {0}\", episode_id)\n            vpid = self.find_vpid(self.url)\n            if vpid:\n                log.debug(\"Found VPID: {0}\", vpid)\n                for s in self.mediaselector(vpid):\n                    yield s\n            else:\n                log.error(\"Could not find VPID for episode {0}\",\n                          episode_id)\n        elif channel_name:\n            log.debug(\"Loading stream for live channel: {0}\", channel_name)\n            if self.get_option(\"hd\"):\n                tvip = self.find_tvip(self.url, master=True) + \"_hd\"\n                if tvip:\n                    log.debug(\"Trying HD stream {0}...\", tvip)\n                    try:\n                        for s in self.mediaselector(tvip):\n                            yield s\n                    except PluginError:\n                        log.error(\n                            \"Failed to get HD streams, falling back to SD\")\n                    else:\n                        return\n            tvip = self.find_tvip(self.url)\n            if tvip:\n                log.debug(\"Found TVIP: {0}\", tvip)\n                for s in self.mediaselector(tvip):\n                    yield s\n\n\n__plugin__ = BBCiPlayer\n", "path": "src/streamlink/plugins/bbciplayer.py"}]}