We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BBC iplayer keyerror: goto
### Plugin Issue

- [x] This is a plugin issue and I have read the contribution guidelines.

### Description

When trying to connect to bbc iplayer, live stream of bbc channel, it crashes with KeyError: 'goto'


### Reproduction steps / Explicit stream URLs to test

1. i updated streamlink to latest from github: pip-3.6 install --user git+https://github.com/streamlink/streamlink.git
2. i ran the following cmd: streamlink --loglevel debug --http-no-ssl-verify --bbciplayer-username "username" --bbciplayer-password password --player-external-http --player-external-http-port 3280 https://www.bbc.co.uk/iplayer/live/bbctwo best


### Log output

```
[cli][debug] OS:         FreeBSD-12.0-RELEASE-p3-amd64-64bit-ELF
[cli][debug] Python:     3.6.8
[cli][debug] Streamlink: 1.2.0+12.g1fb9391
[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.50.0)
[cli][info] Found matching plugin bbciplayer for URL https://www.bbc.co.uk/iplayer/live/bbctwo
[cli][debug] Plugin specific arguments:
[cli][debug]  --bbciplayer-username=***@*** (username)
[cli][debug]  --bbciplayer-password=******** (password)
[plugin.bbciplayer][info] A TV License is required to watch BBC iPlayer streams, see the BBC website for more information: https://www.bbc.co.uk/iplayer/help/tvlicence
Traceback (most recent call last):
  File "/home/username/bin/streamlink", line 11, in <module>
    load_entry_point('streamlink==1.2.0+12.g1fb9391', 'console_scripts', 'streamlink')()
  File "/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py", line 1038, in main
    handle_url()
  File "/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py", line 578, in handle_url
    streams = fetch_streams(plugin)
  File "/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py", line 458, in fetch_streams
    sorting_excludes=args.stream_sorting_excludes)
  File "/home/username/.local/lib/python3.6/site-packages/streamlink/plugin/plugin.py", line 323, in streams
    ostreams = list(ostreams)
  File "/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py", line 225, in _get_streams
    if not self.login(self.url):
  File "/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py", line 199, in login
    http_nonce = self._extract_nonce(session_res)
  File "/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py", line 112, in _extract_nonce
    final_url = urlparse(last_redirect_query['goto'])
KeyError: 'goto'

```

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

{"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@@ -31,8 +31,7 @@\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+    state_re = re.compile(r'window.__IPLAYER_REDUX_STATE__\\s*=\\s*({.*});')\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@@ -96,8 +95,8 @@\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+        Given an HTTP response from the session 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 include them in the auth requests.\n \n         :param http_result: HTTP response from the bbc session endpoint.\n         :type http_result: requests.Response\n@@ -105,16 +104,9 @@\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+        p = urlparse(http_result.url)\n+        d = dict(parse_qsl(p.query))\n+        return d.get(\"nonce\")\n \n     def find_vpid(self, url, res=None):\n         \"\"\"\n@@ -135,13 +127,15 @@\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+        log.debug(\"Looking for {0} tvip on {1}\".format(\"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+        m = self.state_re.search(res.text)\n+        data = m and parse_json(m.group(1))\n+        if data:\n+            channel = data.get(\"channel\")\n+            if master:\n+                return channel.get(\"masterBrand\")\n+            return channel.get(\"id\")\n \n     def mediaselector(self, vpid):\n         urls = defaultdict(set)\n@@ -183,6 +177,7 @@\n         :return: Whether authentication was successful\n         :rtype: bool\n         \"\"\"\n+\n         def auth_check(res):\n             return ptrt_url in ([h.url for h in res.history] + [res.url])\n", "issue": "BBC iplayer keyerror: goto\n### Plugin Issue\r\n\r\n- [x] This is a plugin issue and I have read the contribution guidelines.\r\n\r\n### Description\r\n\r\nWhen trying to connect to bbc iplayer, live stream of bbc channel, it crashes with KeyError: 'goto'\r\n\r\n\r\n### Reproduction steps / Explicit stream URLs to test\r\n\r\n1. i updated streamlink to latest from github: pip-3.6 install --user git+https://github.com/streamlink/streamlink.git\r\n2. i ran the following cmd: streamlink --loglevel debug --http-no-ssl-verify --bbciplayer-username \"username\" --bbciplayer-password password --player-external-http --player-external-http-port 3280 https://www.bbc.co.uk/iplayer/live/bbctwo best\r\n\r\n\r\n### Log output\r\n\r\n```\r\n[cli][debug] OS:         FreeBSD-12.0-RELEASE-p3-amd64-64bit-ELF\r\n[cli][debug] Python:     3.6.8\r\n[cli][debug] Streamlink: 1.2.0+12.g1fb9391\r\n[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.50.0)\r\n[cli][info] Found matching plugin bbciplayer for URL https://www.bbc.co.uk/iplayer/live/bbctwo\r\n[cli][debug] Plugin specific arguments:\r\n[cli][debug]  --bbciplayer-username=***@*** (username)\r\n[cli][debug]  --bbciplayer-password=******** (password)\r\n[plugin.bbciplayer][info] A TV License is required to watch BBC iPlayer streams, see the BBC website for more information: https://www.bbc.co.uk/iplayer/help/tvlicence\r\nTraceback (most recent call last):\r\n  File \"/home/username/bin/streamlink\", line 11, in <module>\r\n    load_entry_point('streamlink==1.2.0+12.g1fb9391', 'console_scripts', 'streamlink')()\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py\", line 1038, in main\r\n    handle_url()\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py\", line 578, in handle_url\r\n    streams = fetch_streams(plugin)\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink_cli/main.py\", line 458, in fetch_streams\r\n    sorting_excludes=args.stream_sorting_excludes)\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink/plugin/plugin.py\", line 323, in streams\r\n    ostreams = list(ostreams)\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py\", line 225, in _get_streams\r\n    if not self.login(self.url):\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py\", line 199, in login\r\n    http_nonce = self._extract_nonce(session_res)\r\n  File \"/home/username/.local/lib/python3.6/site-packages/streamlink/plugins/bbciplayer.py\", line 112, in _extract_nonce\r\n    final_url = urlparse(last_redirect_query['goto'])\r\nKeyError: 'goto'\r\n\r\n```\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    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"}], "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    state_re = re.compile(r'window.__IPLAYER_REDUX_STATE__\\s*=\\s*({.*});')\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 session 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 include 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        p = urlparse(http_result.url)\n        d = dict(parse_qsl(p.query))\n        return d.get(\"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}\".format(\"master\" if master else \"\", url))\n        res = self.session.http.get(url)\n        m = self.state_re.search(res.text)\n        data = m and parse_json(m.group(1))\n        if data:\n            channel = data.get(\"channel\")\n            if master:\n                return channel.get(\"masterBrand\")\n            return channel.get(\"id\")\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\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"}]}