We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Adult Swim doesn't play any streams.
https://www.adultswim.com/videos/streams
https://www.adultswim.com/videos/streams/toonami
https://www.adultswim.com/videos/streams/williams-stream

--- 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/stream/hls.py`
Content:
```
1 from collections import defaultdict, namedtuple
2 
3 try:
4     from Crypto.Cipher import AES
5     import struct
6 
7     def num_to_iv(n):
8         return struct.pack(">8xq", n)
9 
10     CAN_DECRYPT = True
11 except ImportError:
12     CAN_DECRYPT = False
13 
14 from . import hls_playlist
15 from .http import HTTPStream
16 from .segmented import (SegmentedStreamReader,
17                         SegmentedStreamWriter,
18                         SegmentedStreamWorker)
19 from ..exceptions import StreamError
20 
21 
22 Sequence = namedtuple("Sequence", "num segment")
23 
24 
25 class HLSStreamWriter(SegmentedStreamWriter):
26     def __init__(self, reader, *args, **kwargs):
27         options = reader.stream.session.options
28         kwargs["retries"] = options.get("hls-segment-attempts")
29         kwargs["threads"] = options.get("hls-segment-threads")
30         kwargs["timeout"] = options.get("hls-segment-timeout")
31         SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)
32 
33         self.byterange_offsets = defaultdict(int)
34         self.key_data = None
35         self.key_uri = None
36 
37     def create_decryptor(self, key, sequence):
38         if key.method != "AES-128":
39             raise StreamError("Unable to decrypt cipher {0}", key.method)
40 
41         if not key.uri:
42             raise StreamError("Missing URI to decryption key")
43 
44         if self.key_uri != key.uri:
45             res = self.session.http.get(key.uri, exception=StreamError,
46                                         **self.reader.request_params)
47             self.key_data = res.content
48             self.key_uri = key.uri
49 
50         iv = key.iv or num_to_iv(sequence)
51 
52         # Pad IV if needed
53         iv = b"\x00" * (16 - len(iv)) + iv
54 
55         return AES.new(self.key_data, AES.MODE_CBC, iv)
56 
57     def create_request_params(self, sequence):
58         request_params = dict(self.reader.request_params)
59         headers = request_params.pop("headers", {})
60 
61         if sequence.segment.byterange:
62             bytes_start = self.byterange_offsets[sequence.segment.uri]
63             if sequence.segment.byterange.offset is not None:
64                 bytes_start = sequence.segment.byterange.offset
65 
66             bytes_len = max(sequence.segment.byterange.range - 1, 0)
67             bytes_end = bytes_start + bytes_len
68             headers["Range"] = "bytes={0}-{1}".format(bytes_start, bytes_end)
69             self.byterange_offsets[sequence.segment.uri] = bytes_end + 1
70 
71         request_params["headers"] = headers
72 
73         return request_params
74 
75     def fetch(self, sequence, retries=None):
76         if self.closed or not retries:
77             return
78 
79         try:
80             request_params = self.create_request_params(sequence)
81             return self.session.http.get(sequence.segment.uri,
82                                          stream=True,
83                                          timeout=self.timeout,
84                                          exception=StreamError,
85                                          **request_params)
86         except StreamError as err:
87             self.logger.error("Failed to open segment {0}: {1}", sequence.num, err)
88             return self.fetch(sequence, retries - 1)
89 
90     def write(self, sequence, res, chunk_size=8192, retries=None):
91         retries = retries or self.retries
92         if retries == 0:
93             self.logger.error("Failed to open segment {0}", sequence.num)
94             return
95         try:
96             if sequence.segment.key and sequence.segment.key.method != "NONE":
97                 try:
98                     decryptor = self.create_decryptor(sequence.segment.key,
99                                                       sequence.num)
100                 except StreamError as err:
101                     self.logger.error("Failed to create decryptor: {0}", err)
102                     self.close()
103                     return
104 
105                 for chunk in res.iter_content(chunk_size):
106                     # If the input data is not a multiple of 16, cut off any garbage
107                     garbage_len = len(chunk) % 16
108                     if garbage_len:
109                         self.logger.debug("Cutting off {0} bytes of garbage "
110                                           "before decrypting", garbage_len)
111                         decrypted_chunk = decryptor.decrypt(chunk[:-garbage_len])
112                     else:
113                         decrypted_chunk = decryptor.decrypt(chunk)
114                     self.reader.buffer.write(decrypted_chunk)
115             else:
116                 for chunk in res.iter_content(chunk_size):
117                     self.reader.buffer.write(chunk)
118         except StreamError as err:
119             self.logger.error("Failed to open segment {0}: {1}", sequence.num, err)
120             return self.write(sequence,
121                               self.fetch(sequence, retries=self.retries),
122                               chunk_size=chunk_size,
123                               retries=retries - 1)
124 
125         self.logger.debug("Download of segment {0} complete", sequence.num)
126 
127 
128 class HLSStreamWorker(SegmentedStreamWorker):
129     def __init__(self, *args, **kwargs):
130         SegmentedStreamWorker.__init__(self, *args, **kwargs)
131 
132         self.playlist_changed = False
133         self.playlist_end = None
134         self.playlist_sequence = -1
135         self.playlist_sequences = []
136         self.playlist_reload_time = 15
137         self.live_edge = self.session.options.get("hls-live-edge")
138 
139         self.reload_playlist()
140 
141     def reload_playlist(self):
142         if self.closed:
143             return
144 
145         self.reader.buffer.wait_free()
146         self.logger.debug("Reloading playlist")
147         res = self.session.http.get(self.stream.url,
148                                     exception=StreamError,
149                                     **self.reader.request_params)
150 
151         try:
152             playlist = hls_playlist.load(res.text, res.url)
153         except ValueError as err:
154             raise StreamError(err)
155 
156         if playlist.is_master:
157             raise StreamError("Attempted to play a variant playlist, use "
158                               "'hlsvariant://{0}' instead".format(self.stream.url))
159 
160         if playlist.iframes_only:
161             raise StreamError("Streams containing I-frames only is not playable")
162 
163         media_sequence = playlist.media_sequence or 0
164         sequences = [Sequence(media_sequence + i, s)
165                      for i, s in enumerate(playlist.segments)]
166 
167         if sequences:
168             self.process_sequences(playlist, sequences)
169 
170     def process_sequences(self, playlist, sequences):
171         first_sequence, last_sequence = sequences[0], sequences[-1]
172 
173         if first_sequence.segment.key and first_sequence.segment.key.method != "NONE":
174             self.logger.debug("Segments in this playlist are encrypted")
175 
176             if not CAN_DECRYPT:
177                 raise StreamError("Need pyCrypto or pycryptodome installed to decrypt this stream")
178 
179         self.playlist_changed = ([s.num for s in self.playlist_sequences] !=
180                                  [s.num for s in sequences])
181         self.playlist_reload_time = (playlist.target_duration or
182                                      last_sequence.segment.duration)
183         self.playlist_sequences = sequences
184 
185         if not self.playlist_changed:
186             self.playlist_reload_time = max(self.playlist_reload_time / 2, 1)
187 
188         if playlist.is_endlist:
189             self.playlist_end = last_sequence.num
190 
191         if self.playlist_sequence < 0:
192             if self.playlist_end is None:
193                 edge_index = -(min(len(sequences), max(int(self.live_edge), 1)))
194                 edge_sequence = sequences[edge_index]
195                 self.playlist_sequence = edge_sequence.num
196             else:
197                 self.playlist_sequence = first_sequence.num
198 
199     def valid_sequence(self, sequence):
200         return sequence.num >= self.playlist_sequence
201 
202     def iter_segments(self):
203         while not self.closed:
204             for sequence in filter(self.valid_sequence, self.playlist_sequences):
205                 self.logger.debug("Adding segment {0} to queue", sequence.num)
206                 yield sequence
207 
208                 # End of stream
209                 stream_end = self.playlist_end and sequence.num >= self.playlist_end
210                 if self.closed or stream_end:
211                     return
212 
213                 self.playlist_sequence = sequence.num + 1
214 
215             if self.wait(self.playlist_reload_time):
216                 try:
217                     self.reload_playlist()
218                 except StreamError as err:
219                     self.logger.warning("Failed to reload playlist: {0}", err)
220 
221 
222 class HLSStreamReader(SegmentedStreamReader):
223     __worker__ = HLSStreamWorker
224     __writer__ = HLSStreamWriter
225 
226     def __init__(self, stream, *args, **kwargs):
227         SegmentedStreamReader.__init__(self, stream, *args, **kwargs)
228         self.logger = stream.session.logger.new_module("stream.hls")
229         self.request_params = dict(stream.args)
230         self.timeout = stream.session.options.get("hls-timeout")
231 
232         # These params are reserved for internal use
233         self.request_params.pop("exception", None)
234         self.request_params.pop("stream", None)
235         self.request_params.pop("timeout", None)
236         self.request_params.pop("url", None)
237 
238 
239 class HLSStream(HTTPStream):
240     """Implementation of the Apple HTTP Live Streaming protocol
241 
242     *Attributes:*
243 
244     - :attr:`url` The URL to the HLS playlist.
245     - :attr:`args` A :class:`dict` containing keyword arguments passed
246       to :meth:`requests.request`, such as headers and cookies.
247 
248     .. versionchanged:: 1.7.0
249        Added *args* attribute.
250 
251     """
252 
253     __shortname__ = "hls"
254 
255     def __init__(self, session_, url, **args):
256         HTTPStream.__init__(self, session_, url, **args)
257 
258     def __repr__(self):
259         return "<HLSStream({0!r})>".format(self.url)
260 
261     def __json__(self):
262         json = HTTPStream.__json__(self)
263 
264         # Pretty sure HLS is GET only.
265         del json["method"]
266         del json["body"]
267 
268         return json
269 
270     def open(self):
271         reader = HLSStreamReader(self)
272         reader.open()
273 
274         return reader
275 
276     @classmethod
277     def parse_variant_playlist(cls, session_, url, name_key="name",
278                                name_prefix="", check_streams=False,
279                                **request_params):
280         """Attempts to parse a variant playlist and return its streams.
281 
282         :param url: The URL of the variant playlist.
283         :param name_key: Prefer to use this key as stream name, valid keys are:
284                          name, pixels, bitrate.
285         :param name_prefix: Add this prefix to the stream names.
286         :param check_streams: Only allow streams that are accesible.
287         """
288 
289         # Backwards compatibility with "namekey" and "nameprefix" params.
290         name_key = request_params.pop("namekey", name_key)
291         name_prefix = request_params.pop("nameprefix", name_prefix)
292 
293         res = session_.http.get(url, exception=IOError, **request_params)
294 
295         try:
296             parser = hls_playlist.load(res.text, base_uri=res.url)
297         except ValueError as err:
298             raise IOError("Failed to parse playlist: {0}".format(err))
299 
300         streams = {}
301         for playlist in filter(lambda p: not p.is_iframe, parser.playlists):
302             names = dict(name=None, pixels=None, bitrate=None)
303 
304             for media in playlist.media:
305                 if media.type == "VIDEO" and media.name:
306                     names["name"] = media.name
307 
308             if playlist.stream_info.resolution:
309                 width, height = playlist.stream_info.resolution
310                 names["pixels"] = "{0}p".format(height)
311 
312             if playlist.stream_info.bandwidth:
313                 bw = playlist.stream_info.bandwidth
314 
315                 if bw >= 1000:
316                     names["bitrate"] = "{0}k".format(int(bw / 1000.0))
317                 else:
318                     names["bitrate"] = "{0}k".format(bw / 1000.0)
319 
320             stream_name = (names.get(name_key) or names.get("name") or
321                            names.get("pixels") or names.get("bitrate"))
322 
323             if not stream_name or stream_name in streams:
324                 continue
325 
326             if check_streams:
327                 try:
328                     session_.http.get(playlist.uri, **request_params)
329                 except Exception:
330                     continue
331 
332             stream = HLSStream(session_, playlist.uri, **request_params)
333             streams[name_prefix + stream_name] = stream
334 
335         return streams
336
```
--- 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/stream/hls.py b/src/streamlink/stream/hls.py
--- a/src/streamlink/stream/hls.py
+++ b/src/streamlink/stream/hls.py
@@ -320,8 +320,17 @@
             stream_name = (names.get(name_key) or names.get("name") or
                            names.get("pixels") or names.get("bitrate"))
 
-            if not stream_name or stream_name in streams:
+            if not stream_name:
                 continue
+            if stream_name in streams:  # rename duplicate streams
+                stream_name = "{0}_alt".format(stream_name)
+                num_alts = len(list(filter(lambda n: n.startswith(stream_name), streams.keys())))
+
+                # We shouldn't need more than 2 alt streams
+                if num_alts >= 2:
+                    continue
+                elif num_alts > 0:
+                    stream_name = "{0}{1}".format(stream_name, num_alts + 1)
 
             if check_streams:
                 try:

{"golden_diff": "diff --git a/src/streamlink/stream/hls.py b/src/streamlink/stream/hls.py\n--- a/src/streamlink/stream/hls.py\n+++ b/src/streamlink/stream/hls.py\n@@ -320,8 +320,17 @@\n             stream_name = (names.get(name_key) or names.get(\"name\") or\n                            names.get(\"pixels\") or names.get(\"bitrate\"))\n \n-            if not stream_name or stream_name in streams:\n+            if not stream_name:\n                 continue\n+            if stream_name in streams:  # rename duplicate streams\n+                stream_name = \"{0}_alt\".format(stream_name)\n+                num_alts = len(list(filter(lambda n: n.startswith(stream_name), streams.keys())))\n+\n+                # We shouldn't need more than 2 alt streams\n+                if num_alts >= 2:\n+                    continue\n+                elif num_alts > 0:\n+                    stream_name = \"{0}{1}\".format(stream_name, num_alts + 1)\n \n             if check_streams:\n                 try:\n", "issue": "Adult Swim doesn't play any streams.\nhttps://www.adultswim.com/videos/streams\r\nhttps://www.adultswim.com/videos/streams/toonami\r\nhttps://www.adultswim.com/videos/streams/williams-stream\n", "before_files": [{"content": "from collections import defaultdict, namedtuple\n\ntry:\n    from Crypto.Cipher import AES\n    import struct\n\n    def num_to_iv(n):\n        return struct.pack(\">8xq\", n)\n\n    CAN_DECRYPT = True\nexcept ImportError:\n    CAN_DECRYPT = False\n\nfrom . import hls_playlist\nfrom .http import HTTPStream\nfrom .segmented import (SegmentedStreamReader,\n                        SegmentedStreamWriter,\n                        SegmentedStreamWorker)\nfrom ..exceptions import StreamError\n\n\nSequence = namedtuple(\"Sequence\", \"num segment\")\n\n\nclass HLSStreamWriter(SegmentedStreamWriter):\n    def __init__(self, reader, *args, **kwargs):\n        options = reader.stream.session.options\n        kwargs[\"retries\"] = options.get(\"hls-segment-attempts\")\n        kwargs[\"threads\"] = options.get(\"hls-segment-threads\")\n        kwargs[\"timeout\"] = options.get(\"hls-segment-timeout\")\n        SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)\n\n        self.byterange_offsets = defaultdict(int)\n        self.key_data = None\n        self.key_uri = None\n\n    def create_decryptor(self, key, sequence):\n        if key.method != \"AES-128\":\n            raise StreamError(\"Unable to decrypt cipher {0}\", key.method)\n\n        if not key.uri:\n            raise StreamError(\"Missing URI to decryption key\")\n\n        if self.key_uri != key.uri:\n            res = self.session.http.get(key.uri, exception=StreamError,\n                                        **self.reader.request_params)\n            self.key_data = res.content\n            self.key_uri = key.uri\n\n        iv = key.iv or num_to_iv(sequence)\n\n        # Pad IV if needed\n        iv = b\"\\x00\" * (16 - len(iv)) + iv\n\n        return AES.new(self.key_data, AES.MODE_CBC, iv)\n\n    def create_request_params(self, sequence):\n        request_params = dict(self.reader.request_params)\n        headers = request_params.pop(\"headers\", {})\n\n        if sequence.segment.byterange:\n            bytes_start = self.byterange_offsets[sequence.segment.uri]\n            if sequence.segment.byterange.offset is not None:\n                bytes_start = sequence.segment.byterange.offset\n\n            bytes_len = max(sequence.segment.byterange.range - 1, 0)\n            bytes_end = bytes_start + bytes_len\n            headers[\"Range\"] = \"bytes={0}-{1}\".format(bytes_start, bytes_end)\n            self.byterange_offsets[sequence.segment.uri] = bytes_end + 1\n\n        request_params[\"headers\"] = headers\n\n        return request_params\n\n    def fetch(self, sequence, retries=None):\n        if self.closed or not retries:\n            return\n\n        try:\n            request_params = self.create_request_params(sequence)\n            return self.session.http.get(sequence.segment.uri,\n                                         stream=True,\n                                         timeout=self.timeout,\n                                         exception=StreamError,\n                                         **request_params)\n        except StreamError as err:\n            self.logger.error(\"Failed to open segment {0}: {1}\", sequence.num, err)\n            return self.fetch(sequence, retries - 1)\n\n    def write(self, sequence, res, chunk_size=8192, retries=None):\n        retries = retries or self.retries\n        if retries == 0:\n            self.logger.error(\"Failed to open segment {0}\", sequence.num)\n            return\n        try:\n            if sequence.segment.key and sequence.segment.key.method != \"NONE\":\n                try:\n                    decryptor = self.create_decryptor(sequence.segment.key,\n                                                      sequence.num)\n                except StreamError as err:\n                    self.logger.error(\"Failed to create decryptor: {0}\", err)\n                    self.close()\n                    return\n\n                for chunk in res.iter_content(chunk_size):\n                    # If the input data is not a multiple of 16, cut off any garbage\n                    garbage_len = len(chunk) % 16\n                    if garbage_len:\n                        self.logger.debug(\"Cutting off {0} bytes of garbage \"\n                                          \"before decrypting\", garbage_len)\n                        decrypted_chunk = decryptor.decrypt(chunk[:-garbage_len])\n                    else:\n                        decrypted_chunk = decryptor.decrypt(chunk)\n                    self.reader.buffer.write(decrypted_chunk)\n            else:\n                for chunk in res.iter_content(chunk_size):\n                    self.reader.buffer.write(chunk)\n        except StreamError as err:\n            self.logger.error(\"Failed to open segment {0}: {1}\", sequence.num, err)\n            return self.write(sequence,\n                              self.fetch(sequence, retries=self.retries),\n                              chunk_size=chunk_size,\n                              retries=retries - 1)\n\n        self.logger.debug(\"Download of segment {0} complete\", sequence.num)\n\n\nclass HLSStreamWorker(SegmentedStreamWorker):\n    def __init__(self, *args, **kwargs):\n        SegmentedStreamWorker.__init__(self, *args, **kwargs)\n\n        self.playlist_changed = False\n        self.playlist_end = None\n        self.playlist_sequence = -1\n        self.playlist_sequences = []\n        self.playlist_reload_time = 15\n        self.live_edge = self.session.options.get(\"hls-live-edge\")\n\n        self.reload_playlist()\n\n    def reload_playlist(self):\n        if self.closed:\n            return\n\n        self.reader.buffer.wait_free()\n        self.logger.debug(\"Reloading playlist\")\n        res = self.session.http.get(self.stream.url,\n                                    exception=StreamError,\n                                    **self.reader.request_params)\n\n        try:\n            playlist = hls_playlist.load(res.text, res.url)\n        except ValueError as err:\n            raise StreamError(err)\n\n        if playlist.is_master:\n            raise StreamError(\"Attempted to play a variant playlist, use \"\n                              \"'hlsvariant://{0}' instead\".format(self.stream.url))\n\n        if playlist.iframes_only:\n            raise StreamError(\"Streams containing I-frames only is not playable\")\n\n        media_sequence = playlist.media_sequence or 0\n        sequences = [Sequence(media_sequence + i, s)\n                     for i, s in enumerate(playlist.segments)]\n\n        if sequences:\n            self.process_sequences(playlist, sequences)\n\n    def process_sequences(self, playlist, sequences):\n        first_sequence, last_sequence = sequences[0], sequences[-1]\n\n        if first_sequence.segment.key and first_sequence.segment.key.method != \"NONE\":\n            self.logger.debug(\"Segments in this playlist are encrypted\")\n\n            if not CAN_DECRYPT:\n                raise StreamError(\"Need pyCrypto or pycryptodome installed to decrypt this stream\")\n\n        self.playlist_changed = ([s.num for s in self.playlist_sequences] !=\n                                 [s.num for s in sequences])\n        self.playlist_reload_time = (playlist.target_duration or\n                                     last_sequence.segment.duration)\n        self.playlist_sequences = sequences\n\n        if not self.playlist_changed:\n            self.playlist_reload_time = max(self.playlist_reload_time / 2, 1)\n\n        if playlist.is_endlist:\n            self.playlist_end = last_sequence.num\n\n        if self.playlist_sequence < 0:\n            if self.playlist_end is None:\n                edge_index = -(min(len(sequences), max(int(self.live_edge), 1)))\n                edge_sequence = sequences[edge_index]\n                self.playlist_sequence = edge_sequence.num\n            else:\n                self.playlist_sequence = first_sequence.num\n\n    def valid_sequence(self, sequence):\n        return sequence.num >= self.playlist_sequence\n\n    def iter_segments(self):\n        while not self.closed:\n            for sequence in filter(self.valid_sequence, self.playlist_sequences):\n                self.logger.debug(\"Adding segment {0} to queue\", sequence.num)\n                yield sequence\n\n                # End of stream\n                stream_end = self.playlist_end and sequence.num >= self.playlist_end\n                if self.closed or stream_end:\n                    return\n\n                self.playlist_sequence = sequence.num + 1\n\n            if self.wait(self.playlist_reload_time):\n                try:\n                    self.reload_playlist()\n                except StreamError as err:\n                    self.logger.warning(\"Failed to reload playlist: {0}\", err)\n\n\nclass HLSStreamReader(SegmentedStreamReader):\n    __worker__ = HLSStreamWorker\n    __writer__ = HLSStreamWriter\n\n    def __init__(self, stream, *args, **kwargs):\n        SegmentedStreamReader.__init__(self, stream, *args, **kwargs)\n        self.logger = stream.session.logger.new_module(\"stream.hls\")\n        self.request_params = dict(stream.args)\n        self.timeout = stream.session.options.get(\"hls-timeout\")\n\n        # These params are reserved for internal use\n        self.request_params.pop(\"exception\", None)\n        self.request_params.pop(\"stream\", None)\n        self.request_params.pop(\"timeout\", None)\n        self.request_params.pop(\"url\", None)\n\n\nclass HLSStream(HTTPStream):\n    \"\"\"Implementation of the Apple HTTP Live Streaming protocol\n\n    *Attributes:*\n\n    - :attr:`url` The URL to the HLS playlist.\n    - :attr:`args` A :class:`dict` containing keyword arguments passed\n      to :meth:`requests.request`, such as headers and cookies.\n\n    .. versionchanged:: 1.7.0\n       Added *args* attribute.\n\n    \"\"\"\n\n    __shortname__ = \"hls\"\n\n    def __init__(self, session_, url, **args):\n        HTTPStream.__init__(self, session_, url, **args)\n\n    def __repr__(self):\n        return \"<HLSStream({0!r})>\".format(self.url)\n\n    def __json__(self):\n        json = HTTPStream.__json__(self)\n\n        # Pretty sure HLS is GET only.\n        del json[\"method\"]\n        del json[\"body\"]\n\n        return json\n\n    def open(self):\n        reader = HLSStreamReader(self)\n        reader.open()\n\n        return reader\n\n    @classmethod\n    def parse_variant_playlist(cls, session_, url, name_key=\"name\",\n                               name_prefix=\"\", check_streams=False,\n                               **request_params):\n        \"\"\"Attempts to parse a variant playlist and return its streams.\n\n        :param url: The URL of the variant playlist.\n        :param name_key: Prefer to use this key as stream name, valid keys are:\n                         name, pixels, bitrate.\n        :param name_prefix: Add this prefix to the stream names.\n        :param check_streams: Only allow streams that are accesible.\n        \"\"\"\n\n        # Backwards compatibility with \"namekey\" and \"nameprefix\" params.\n        name_key = request_params.pop(\"namekey\", name_key)\n        name_prefix = request_params.pop(\"nameprefix\", name_prefix)\n\n        res = session_.http.get(url, exception=IOError, **request_params)\n\n        try:\n            parser = hls_playlist.load(res.text, base_uri=res.url)\n        except ValueError as err:\n            raise IOError(\"Failed to parse playlist: {0}\".format(err))\n\n        streams = {}\n        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):\n            names = dict(name=None, pixels=None, bitrate=None)\n\n            for media in playlist.media:\n                if media.type == \"VIDEO\" and media.name:\n                    names[\"name\"] = media.name\n\n            if playlist.stream_info.resolution:\n                width, height = playlist.stream_info.resolution\n                names[\"pixels\"] = \"{0}p\".format(height)\n\n            if playlist.stream_info.bandwidth:\n                bw = playlist.stream_info.bandwidth\n\n                if bw >= 1000:\n                    names[\"bitrate\"] = \"{0}k\".format(int(bw / 1000.0))\n                else:\n                    names[\"bitrate\"] = \"{0}k\".format(bw / 1000.0)\n\n            stream_name = (names.get(name_key) or names.get(\"name\") or\n                           names.get(\"pixels\") or names.get(\"bitrate\"))\n\n            if not stream_name or stream_name in streams:\n                continue\n\n            if check_streams:\n                try:\n                    session_.http.get(playlist.uri, **request_params)\n                except Exception:\n                    continue\n\n            stream = HLSStream(session_, playlist.uri, **request_params)\n            streams[name_prefix + stream_name] = stream\n\n        return streams\n", "path": "src/streamlink/stream/hls.py"}], "after_files": [{"content": "from collections import defaultdict, namedtuple\n\ntry:\n    from Crypto.Cipher import AES\n    import struct\n\n    def num_to_iv(n):\n        return struct.pack(\">8xq\", n)\n\n    CAN_DECRYPT = True\nexcept ImportError:\n    CAN_DECRYPT = False\n\nfrom . import hls_playlist\nfrom .http import HTTPStream\nfrom .segmented import (SegmentedStreamReader,\n                        SegmentedStreamWriter,\n                        SegmentedStreamWorker)\nfrom ..exceptions import StreamError\n\n\nSequence = namedtuple(\"Sequence\", \"num segment\")\n\n\nclass HLSStreamWriter(SegmentedStreamWriter):\n    def __init__(self, reader, *args, **kwargs):\n        options = reader.stream.session.options\n        kwargs[\"retries\"] = options.get(\"hls-segment-attempts\")\n        kwargs[\"threads\"] = options.get(\"hls-segment-threads\")\n        kwargs[\"timeout\"] = options.get(\"hls-segment-timeout\")\n        SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)\n\n        self.byterange_offsets = defaultdict(int)\n        self.key_data = None\n        self.key_uri = None\n\n    def create_decryptor(self, key, sequence):\n        if key.method != \"AES-128\":\n            raise StreamError(\"Unable to decrypt cipher {0}\", key.method)\n\n        if not key.uri:\n            raise StreamError(\"Missing URI to decryption key\")\n\n        if self.key_uri != key.uri:\n            res = self.session.http.get(key.uri, exception=StreamError,\n                                        **self.reader.request_params)\n            self.key_data = res.content\n            self.key_uri = key.uri\n\n        iv = key.iv or num_to_iv(sequence)\n\n        # Pad IV if needed\n        iv = b\"\\x00\" * (16 - len(iv)) + iv\n\n        return AES.new(self.key_data, AES.MODE_CBC, iv)\n\n    def create_request_params(self, sequence):\n        request_params = dict(self.reader.request_params)\n        headers = request_params.pop(\"headers\", {})\n\n        if sequence.segment.byterange:\n            bytes_start = self.byterange_offsets[sequence.segment.uri]\n            if sequence.segment.byterange.offset is not None:\n                bytes_start = sequence.segment.byterange.offset\n\n            bytes_len = max(sequence.segment.byterange.range - 1, 0)\n            bytes_end = bytes_start + bytes_len\n            headers[\"Range\"] = \"bytes={0}-{1}\".format(bytes_start, bytes_end)\n            self.byterange_offsets[sequence.segment.uri] = bytes_end + 1\n\n        request_params[\"headers\"] = headers\n\n        return request_params\n\n    def fetch(self, sequence, retries=None):\n        if self.closed or not retries:\n            return\n\n        try:\n            request_params = self.create_request_params(sequence)\n            return self.session.http.get(sequence.segment.uri,\n                                         stream=True,\n                                         timeout=self.timeout,\n                                         exception=StreamError,\n                                         **request_params)\n        except StreamError as err:\n            self.logger.error(\"Failed to open segment {0}: {1}\", sequence.num, err)\n            return self.fetch(sequence, retries - 1)\n\n    def write(self, sequence, res, chunk_size=8192, retries=None):\n        retries = retries or self.retries\n        if retries == 0:\n            self.logger.error(\"Failed to open segment {0}\", sequence.num)\n            return\n        try:\n            if sequence.segment.key and sequence.segment.key.method != \"NONE\":\n                try:\n                    decryptor = self.create_decryptor(sequence.segment.key,\n                                                      sequence.num)\n                except StreamError as err:\n                    self.logger.error(\"Failed to create decryptor: {0}\", err)\n                    self.close()\n                    return\n\n                for chunk in res.iter_content(chunk_size):\n                    # If the input data is not a multiple of 16, cut off any garbage\n                    garbage_len = len(chunk) % 16\n                    if garbage_len:\n                        self.logger.debug(\"Cutting off {0} bytes of garbage \"\n                                          \"before decrypting\", garbage_len)\n                        decrypted_chunk = decryptor.decrypt(chunk[:-garbage_len])\n                    else:\n                        decrypted_chunk = decryptor.decrypt(chunk)\n                    self.reader.buffer.write(decrypted_chunk)\n            else:\n                for chunk in res.iter_content(chunk_size):\n                    self.reader.buffer.write(chunk)\n        except StreamError as err:\n            self.logger.error(\"Failed to open segment {0}: {1}\", sequence.num, err)\n            return self.write(sequence,\n                              self.fetch(sequence, retries=self.retries),\n                              chunk_size=chunk_size,\n                              retries=retries - 1)\n\n        self.logger.debug(\"Download of segment {0} complete\", sequence.num)\n\n\nclass HLSStreamWorker(SegmentedStreamWorker):\n    def __init__(self, *args, **kwargs):\n        SegmentedStreamWorker.__init__(self, *args, **kwargs)\n\n        self.playlist_changed = False\n        self.playlist_end = None\n        self.playlist_sequence = -1\n        self.playlist_sequences = []\n        self.playlist_reload_time = 15\n        self.live_edge = self.session.options.get(\"hls-live-edge\")\n\n        self.reload_playlist()\n\n    def reload_playlist(self):\n        if self.closed:\n            return\n\n        self.reader.buffer.wait_free()\n        self.logger.debug(\"Reloading playlist\")\n        res = self.session.http.get(self.stream.url,\n                                    exception=StreamError,\n                                    **self.reader.request_params)\n\n        try:\n            playlist = hls_playlist.load(res.text, res.url)\n        except ValueError as err:\n            raise StreamError(err)\n\n        if playlist.is_master:\n            raise StreamError(\"Attempted to play a variant playlist, use \"\n                              \"'hlsvariant://{0}' instead\".format(self.stream.url))\n\n        if playlist.iframes_only:\n            raise StreamError(\"Streams containing I-frames only is not playable\")\n\n        media_sequence = playlist.media_sequence or 0\n        sequences = [Sequence(media_sequence + i, s)\n                     for i, s in enumerate(playlist.segments)]\n\n        if sequences:\n            self.process_sequences(playlist, sequences)\n\n    def process_sequences(self, playlist, sequences):\n        first_sequence, last_sequence = sequences[0], sequences[-1]\n\n        if first_sequence.segment.key and first_sequence.segment.key.method != \"NONE\":\n            self.logger.debug(\"Segments in this playlist are encrypted\")\n\n            if not CAN_DECRYPT:\n                raise StreamError(\"Need pyCrypto or pycryptodome installed to decrypt this stream\")\n\n        self.playlist_changed = ([s.num for s in self.playlist_sequences] !=\n                                 [s.num for s in sequences])\n        self.playlist_reload_time = (playlist.target_duration or\n                                     last_sequence.segment.duration)\n        self.playlist_sequences = sequences\n\n        if not self.playlist_changed:\n            self.playlist_reload_time = max(self.playlist_reload_time / 2, 1)\n\n        if playlist.is_endlist:\n            self.playlist_end = last_sequence.num\n\n        if self.playlist_sequence < 0:\n            if self.playlist_end is None:\n                edge_index = -(min(len(sequences), max(int(self.live_edge), 1)))\n                edge_sequence = sequences[edge_index]\n                self.playlist_sequence = edge_sequence.num\n            else:\n                self.playlist_sequence = first_sequence.num\n\n    def valid_sequence(self, sequence):\n        return sequence.num >= self.playlist_sequence\n\n    def iter_segments(self):\n        while not self.closed:\n            for sequence in filter(self.valid_sequence, self.playlist_sequences):\n                self.logger.debug(\"Adding segment {0} to queue\", sequence.num)\n                yield sequence\n\n                # End of stream\n                stream_end = self.playlist_end and sequence.num >= self.playlist_end\n                if self.closed or stream_end:\n                    return\n\n                self.playlist_sequence = sequence.num + 1\n\n            if self.wait(self.playlist_reload_time):\n                try:\n                    self.reload_playlist()\n                except StreamError as err:\n                    self.logger.warning(\"Failed to reload playlist: {0}\", err)\n\n\nclass HLSStreamReader(SegmentedStreamReader):\n    __worker__ = HLSStreamWorker\n    __writer__ = HLSStreamWriter\n\n    def __init__(self, stream, *args, **kwargs):\n        SegmentedStreamReader.__init__(self, stream, *args, **kwargs)\n        self.logger = stream.session.logger.new_module(\"stream.hls\")\n        self.request_params = dict(stream.args)\n        self.timeout = stream.session.options.get(\"hls-timeout\")\n\n        # These params are reserved for internal use\n        self.request_params.pop(\"exception\", None)\n        self.request_params.pop(\"stream\", None)\n        self.request_params.pop(\"timeout\", None)\n        self.request_params.pop(\"url\", None)\n\n\nclass HLSStream(HTTPStream):\n    \"\"\"Implementation of the Apple HTTP Live Streaming protocol\n\n    *Attributes:*\n\n    - :attr:`url` The URL to the HLS playlist.\n    - :attr:`args` A :class:`dict` containing keyword arguments passed\n      to :meth:`requests.request`, such as headers and cookies.\n\n    .. versionchanged:: 1.7.0\n       Added *args* attribute.\n\n    \"\"\"\n\n    __shortname__ = \"hls\"\n\n    def __init__(self, session_, url, **args):\n        HTTPStream.__init__(self, session_, url, **args)\n\n    def __repr__(self):\n        return \"<HLSStream({0!r})>\".format(self.url)\n\n    def __json__(self):\n        json = HTTPStream.__json__(self)\n\n        # Pretty sure HLS is GET only.\n        del json[\"method\"]\n        del json[\"body\"]\n\n        return json\n\n    def open(self):\n        reader = HLSStreamReader(self)\n        reader.open()\n\n        return reader\n\n    @classmethod\n    def parse_variant_playlist(cls, session_, url, name_key=\"name\",\n                               name_prefix=\"\", check_streams=False,\n                               **request_params):\n        \"\"\"Attempts to parse a variant playlist and return its streams.\n\n        :param url: The URL of the variant playlist.\n        :param name_key: Prefer to use this key as stream name, valid keys are:\n                         name, pixels, bitrate.\n        :param name_prefix: Add this prefix to the stream names.\n        :param check_streams: Only allow streams that are accesible.\n        \"\"\"\n\n        # Backwards compatibility with \"namekey\" and \"nameprefix\" params.\n        name_key = request_params.pop(\"namekey\", name_key)\n        name_prefix = request_params.pop(\"nameprefix\", name_prefix)\n\n        res = session_.http.get(url, exception=IOError, **request_params)\n\n        try:\n            parser = hls_playlist.load(res.text, base_uri=res.url)\n        except ValueError as err:\n            raise IOError(\"Failed to parse playlist: {0}\".format(err))\n\n        streams = {}\n        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):\n            names = dict(name=None, pixels=None, bitrate=None)\n\n            for media in playlist.media:\n                if media.type == \"VIDEO\" and media.name:\n                    names[\"name\"] = media.name\n\n            if playlist.stream_info.resolution:\n                width, height = playlist.stream_info.resolution\n                names[\"pixels\"] = \"{0}p\".format(height)\n\n            if playlist.stream_info.bandwidth:\n                bw = playlist.stream_info.bandwidth\n\n                if bw >= 1000:\n                    names[\"bitrate\"] = \"{0}k\".format(int(bw / 1000.0))\n                else:\n                    names[\"bitrate\"] = \"{0}k\".format(bw / 1000.0)\n\n            stream_name = (names.get(name_key) or names.get(\"name\") or\n                           names.get(\"pixels\") or names.get(\"bitrate\"))\n\n            if not stream_name:\n                continue\n            if stream_name in streams:  # rename duplicate streams\n                stream_name = \"{0}_alt\".format(stream_name)\n                num_alts = len(list(filter(lambda n: n.startswith(stream_name), streams.keys())))\n\n                # We shouldn't need more than 2 alt streams\n                if num_alts >= 2:\n                    continue\n                elif num_alts > 0:\n                    stream_name = \"{0}{1}\".format(stream_name, num_alts + 1)\n\n            if check_streams:\n                try:\n                    session_.http.get(playlist.uri, **request_params)\n                except Exception:\n                    continue\n\n            stream = HLSStream(session_, playlist.uri, **request_params)\n            streams[name_prefix + stream_name] = stream\n\n        return streams\n", "path": "src/streamlink/stream/hls.py"}]}