We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Audio Spanish
I don't hear this video in Spanish, why?,  Is there any way I can keep it from being heard in English?
```
https://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/
```



### Logs

```
"C:\Program Files (x86)\Streamlink\bin\streamlink.exe" h
ttps://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/cap
itulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/ best --player ffplay -l d
ebug
[cli][debug] OS:         Windows 7
[cli][debug] Python:     3.5.2
[cli][debug] Streamlink: 0.14.2
[cli][debug] Requests(2.19.1), Socks(1.6.7), Websocket(0.48.0)
[cli][info] Found matching plugin atresplayer for URL https://www.atresplayer.co
m/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mu
jer_5b364bee7ed1a8dd360b5b7b/
[plugin.atresplayer][debug] API URL: https://api.atresplayer.com/client/v1/playe
r/episode/5b364bee7ed1a8dd360b5b7b
[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n
et/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/hls.smi
l/playlist.m3u8?pulse=assets4%2F2018%2F06%2F29%2F7875368D-BF45-4012-9EBC-C4F7898
4B672%2F%7C1531195580%7Cb6e79f7291966a25070a79138cd19cb9 (application/vnd.apple.
mpegurl)
[stream.hls][debug] Using external audio tracks for stream 1080p (language=es, n
ame=Spanish)
[stream.hls][debug] Using external audio tracks for stream 720p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 480p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 360p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 240p (language=es, na
me=Spanish)
[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n
et/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/dash.sm
il/manifest_mvlist.mpd (application/dash+xml)
[cli][info] Available streams: 240p (worst), 240p+a128k, 360p, 360p+a128k, 480p,
 480p+a128k, 720p, 720p+a128k, 1080p, 1080p+a128k (best)
[cli][info] Starting player: ffplay
....................
....................
....................
```

--- 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/dash.py`
Content:
```
1 import itertools
2 import logging
3 import datetime
4 import os.path
5 
6 import requests
7 from streamlink import StreamError, PluginError
8 from streamlink.compat import urlparse, urlunparse
9 from streamlink.stream.http import valid_args, normalize_key
10 from streamlink.stream.stream import Stream
11 from streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline
12 from streamlink.stream.ffmpegmux import FFMPEGMuxer
13 from streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter
14 
15 log = logging.getLogger(__name__)
16 
17 
18 class DASHStreamWriter(SegmentedStreamWriter):
19     def __init__(self, reader, *args, **kwargs):
20         options = reader.stream.session.options
21         kwargs["retries"] = options.get("dash-segment-attempts")
22         kwargs["threads"] = options.get("dash-segment-threads")
23         kwargs["timeout"] = options.get("dash-segment-timeout")
24         SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)
25 
26     def fetch(self, segment, retries=None):
27         if self.closed or not retries:
28             return
29 
30         try:
31             headers = {}
32             now = datetime.datetime.now(tz=utc)
33             if segment.available_at > now:
34                 time_to_wait = (segment.available_at - now).total_seconds()
35                 fname = os.path.basename(urlparse(segment.url).path)
36                 log.debug("Waiting for segment: {fname} ({wait:.01f}s)".format(fname=fname, wait=time_to_wait))
37                 sleep_until(segment.available_at)
38 
39             if segment.range:
40                 start, length = segment.range
41                 if length:
42                     end = start + length - 1
43                 else:
44                     end = ""
45                 headers["Range"] = "bytes={0}-{1}".format(start, end)
46 
47             return self.session.http.get(segment.url,
48                                          timeout=self.timeout,
49                                          exception=StreamError,
50                                          headers=headers)
51         except StreamError as err:
52             log.error("Failed to open segment {0}: {1}", segment.url, err)
53             return self.fetch(segment, retries - 1)
54 
55     def write(self, segment, res, chunk_size=8192):
56         for chunk in res.iter_content(chunk_size):
57             if not self.closed:
58                 self.reader.buffer.write(chunk)
59             else:
60                 log.warning("Download of segment: {} aborted".format(segment.url))
61                 return
62 
63         log.debug("Download of segment: {} complete".format(segment.url))
64 
65 
66 class DASHStreamWorker(SegmentedStreamWorker):
67     def __init__(self, *args, **kwargs):
68         SegmentedStreamWorker.__init__(self, *args, **kwargs)
69         self.mpd = self.stream.mpd
70         self.period = self.stream.period
71 
72     @staticmethod
73     def get_representation(mpd, representation_id, mime_type):
74         for aset in mpd.periods[0].adaptationSets:
75             for rep in aset.representations:
76                 if rep.id == representation_id and rep.mimeType == mime_type:
77                     return rep
78 
79     def iter_segments(self):
80         init = True
81         back_off_factor = 1
82         while not self.closed:
83             # find the representation by ID
84             representation = self.get_representation(self.mpd, self.reader.representation_id, self.reader.mime_type)
85             refresh_wait = max(self.mpd.minimumUpdatePeriod.total_seconds(),
86                                self.mpd.periods[0].duration.total_seconds()) or 5
87             with sleeper(refresh_wait * back_off_factor):
88                 if representation:
89                     for segment in representation.segments(init=init):
90                         if self.closed:
91                             break
92                         yield segment
93                         # log.debug("Adding segment {0} to queue", segment.url)
94 
95                     if self.mpd.type == "dynamic":
96                         if not self.reload():
97                             back_off_factor = max(back_off_factor * 1.3, 10.0)
98                         else:
99                             back_off_factor = 1
100                     else:
101                         return
102                     init = False
103 
104     def reload(self):
105         if self.closed:
106             return
107 
108         self.reader.buffer.wait_free()
109         log.debug("Reloading manifest ({0}:{1})".format(self.reader.representation_id, self.reader.mime_type))
110         res = self.session.http.get(self.mpd.url, exception=StreamError)
111 
112         new_mpd = MPD(self.session.http.xml(res, ignore_ns=True),
113                       base_url=self.mpd.base_url,
114                       url=self.mpd.url,
115                       timelines=self.mpd.timelines)
116 
117         new_rep = self.get_representation(new_mpd, self.reader.representation_id, self.reader.mime_type)
118         with freeze_timeline(new_mpd):
119             changed = len(list(itertools.islice(new_rep.segments(), 1))) > 0
120 
121         if changed:
122             self.mpd = new_mpd
123 
124         return changed
125 
126 
127 class DASHStreamReader(SegmentedStreamReader):
128     __worker__ = DASHStreamWorker
129     __writer__ = DASHStreamWriter
130 
131     def __init__(self, stream, representation_id, mime_type, *args, **kwargs):
132         SegmentedStreamReader.__init__(self, stream, *args, **kwargs)
133         self.mime_type = mime_type
134         self.representation_id = representation_id
135         log.debug("Opening DASH reader for: {0} ({1})".format(self.representation_id, self.mime_type))
136 
137 
138 
139 class DASHStream(Stream):
140     __shortname__ = "dash"
141 
142     def __init__(self,
143                  session,
144                  mpd,
145                  video_representation=None,
146                  audio_representation=None,
147                  period=0,
148                  **args):
149         super(DASHStream, self).__init__(session)
150         self.mpd = mpd
151         self.video_representation = video_representation
152         self.audio_representation = audio_representation
153         self.period = period
154         self.args = args
155 
156     def __json__(self):
157         req = requests.Request(method="GET", url=self.mpd.url, **valid_args(self.args))
158         req = req.prepare()
159 
160         headers = dict(map(normalize_key, req.headers.items()))
161         return dict(type=type(self).shortname(), url=req.url, headers=headers)
162 
163     @classmethod
164     def parse_manifest(cls, session, url, **args):
165         """
166         Attempt to parse a DASH manifest file and return its streams
167 
168         :param session: Streamlink session instance
169         :param url: URL of the manifest file
170         :return: a dict of name -> DASHStream instances
171         """
172         ret = {}
173         res = session.http.get(url, **args)
174         url = res.url
175 
176         urlp = list(urlparse(url))
177         urlp[2], _ = urlp[2].rsplit("/", 1)
178 
179         mpd = MPD(session.http.xml(res, ignore_ns=True), base_url=urlunparse(urlp), url=url)
180 
181         video, audio = [], []
182 
183         # Search for suitable video and audio representations
184         for aset in mpd.periods[0].adaptationSets:
185             if aset.contentProtection:
186                 raise PluginError("{} is protected by DRM".format(url))
187             for rep in aset.representations:
188                 if rep.mimeType.startswith("video"):
189                     video.append(rep)
190                 elif rep.mimeType.startswith("audio"):
191                     audio.append(rep)
192 
193         if not video:
194             video = [None]
195 
196         if not audio:
197             audio = [None]
198 
199         for vid, aud in itertools.product(video, audio):
200             stream = DASHStream(session, mpd, vid, aud, **args)
201             stream_name = []
202 
203             if vid:
204                 stream_name.append("{:0.0f}{}".format(vid.height or vid.bandwidth, "p" if vid.height else "k"))
205             if audio and len(audio) > 1:
206                 stream_name.append("a{:0.0f}k".format(aud.bandwidth))
207             ret['+'.join(stream_name)] = stream
208         return ret
209 
210     def open(self):
211         if self.video_representation:
212             video = DASHStreamReader(self, self.video_representation.id, self.video_representation.mimeType)
213             video.open()
214 
215         if self.audio_representation:
216             audio = DASHStreamReader(self, self.audio_representation.id, self.audio_representation.mimeType)
217             audio.open()
218 
219         if self.video_representation and self.audio_representation:
220             return FFMPEGMuxer(self.session, video, audio, copyts=True).open()
221         elif self.video_representation:
222             return video
223         elif self.audio_representation:
224             return audio
225 
226     def to_url(self):
227         return self.mpd.url
228
```
--- 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/dash.py b/src/streamlink/stream/dash.py
--- a/src/streamlink/stream/dash.py
+++ b/src/streamlink/stream/dash.py
@@ -11,6 +11,7 @@
 from streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline
 from streamlink.stream.ffmpegmux import FFMPEGMuxer
 from streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter
+from streamlink.utils.l10n import Language
 
 log = logging.getLogger(__name__)
 
@@ -196,6 +197,31 @@
         if not audio:
             audio = [None]
 
+        locale = session.localization
+        locale_lang = locale.language
+        lang = None
+        available_languages = set()
+
+        # if the locale is explicitly set, prefer that language over others
+        for aud in audio:
+            if aud and aud.lang:
+                available_languages.add(aud.lang)
+                try:
+                    if locale.explicit and aud.lang and Language.get(aud.lang) == locale_lang:
+                        lang = aud.lang
+                except LookupError:
+                    continue
+
+        if not lang:
+            # filter by the first language that appears
+            lang = audio[0] and audio[0].lang
+
+        log.debug("Available languages for DASH audio streams: {0} (using: {1})".format(", ".join(available_languages) or "NONE", lang or "n/a"))
+
+        # if the language is given by the stream, filter out other languages that do not match
+        if len(available_languages) > 1:
+            audio = list(filter(lambda a: a.lang is None or a.lang == lang, audio))
+
         for vid, aud in itertools.product(video, audio):
             stream = DASHStream(session, mpd, vid, aud, **args)
             stream_name = []

{"golden_diff": "diff --git a/src/streamlink/stream/dash.py b/src/streamlink/stream/dash.py\n--- a/src/streamlink/stream/dash.py\n+++ b/src/streamlink/stream/dash.py\n@@ -11,6 +11,7 @@\n from streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline\n from streamlink.stream.ffmpegmux import FFMPEGMuxer\n from streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter\n+from streamlink.utils.l10n import Language\n \n log = logging.getLogger(__name__)\n \n@@ -196,6 +197,31 @@\n         if not audio:\n             audio = [None]\n \n+        locale = session.localization\n+        locale_lang = locale.language\n+        lang = None\n+        available_languages = set()\n+\n+        # if the locale is explicitly set, prefer that language over others\n+        for aud in audio:\n+            if aud and aud.lang:\n+                available_languages.add(aud.lang)\n+                try:\n+                    if locale.explicit and aud.lang and Language.get(aud.lang) == locale_lang:\n+                        lang = aud.lang\n+                except LookupError:\n+                    continue\n+\n+        if not lang:\n+            # filter by the first language that appears\n+            lang = audio[0] and audio[0].lang\n+\n+        log.debug(\"Available languages for DASH audio streams: {0} (using: {1})\".format(\", \".join(available_languages) or \"NONE\", lang or \"n/a\"))\n+\n+        # if the language is given by the stream, filter out other languages that do not match\n+        if len(available_languages) > 1:\n+            audio = list(filter(lambda a: a.lang is None or a.lang == lang, audio))\n+\n         for vid, aud in itertools.product(video, audio):\n             stream = DASHStream(session, mpd, vid, aud, **args)\n             stream_name = []\n", "issue": "Audio Spanish\nI don't hear this video in Spanish, why?,  Is there any way I can keep it from being heard in English?\r\n```\r\nhttps://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/\r\n```\r\n\r\n\r\n\r\n### Logs\r\n\r\n```\r\n\"C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe\" h\r\nttps://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/cap\r\nitulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/ best --player ffplay -l d\r\nebug\r\n[cli][debug] OS:         Windows 7\r\n[cli][debug] Python:     3.5.2\r\n[cli][debug] Streamlink: 0.14.2\r\n[cli][debug] Requests(2.19.1), Socks(1.6.7), Websocket(0.48.0)\r\n[cli][info] Found matching plugin atresplayer for URL https://www.atresplayer.co\r\nm/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mu\r\njer_5b364bee7ed1a8dd360b5b7b/\r\n[plugin.atresplayer][debug] API URL: https://api.atresplayer.com/client/v1/playe\r\nr/episode/5b364bee7ed1a8dd360b5b7b\r\n[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n\r\net/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/hls.smi\r\nl/playlist.m3u8?pulse=assets4%2F2018%2F06%2F29%2F7875368D-BF45-4012-9EBC-C4F7898\r\n4B672%2F%7C1531195580%7Cb6e79f7291966a25070a79138cd19cb9 (application/vnd.apple.\r\nmpegurl)\r\n[stream.hls][debug] Using external audio tracks for stream 1080p (language=es, n\r\name=Spanish)\r\n[stream.hls][debug] Using external audio tracks for stream 720p (language=es, na\r\nme=Spanish)\r\n[stream.hls][debug] Using external audio tracks for stream 480p (language=es, na\r\nme=Spanish)\r\n[stream.hls][debug] Using external audio tracks for stream 360p (language=es, na\r\nme=Spanish)\r\n[stream.hls][debug] Using external audio tracks for stream 240p (language=es, na\r\nme=Spanish)\r\n[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n\r\net/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/dash.sm\r\nil/manifest_mvlist.mpd (application/dash+xml)\r\n[cli][info] Available streams: 240p (worst), 240p+a128k, 360p, 360p+a128k, 480p,\r\n 480p+a128k, 720p, 720p+a128k, 1080p, 1080p+a128k (best)\r\n[cli][info] Starting player: ffplay\r\n....................\r\n....................\r\n....................\r\n```\n", "before_files": [{"content": "import itertools\nimport logging\nimport datetime\nimport os.path\n\nimport requests\nfrom streamlink import StreamError, PluginError\nfrom streamlink.compat import urlparse, urlunparse\nfrom streamlink.stream.http import valid_args, normalize_key\nfrom streamlink.stream.stream import Stream\nfrom streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline\nfrom streamlink.stream.ffmpegmux import FFMPEGMuxer\nfrom streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter\n\nlog = logging.getLogger(__name__)\n\n\nclass DASHStreamWriter(SegmentedStreamWriter):\n    def __init__(self, reader, *args, **kwargs):\n        options = reader.stream.session.options\n        kwargs[\"retries\"] = options.get(\"dash-segment-attempts\")\n        kwargs[\"threads\"] = options.get(\"dash-segment-threads\")\n        kwargs[\"timeout\"] = options.get(\"dash-segment-timeout\")\n        SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)\n\n    def fetch(self, segment, retries=None):\n        if self.closed or not retries:\n            return\n\n        try:\n            headers = {}\n            now = datetime.datetime.now(tz=utc)\n            if segment.available_at > now:\n                time_to_wait = (segment.available_at - now).total_seconds()\n                fname = os.path.basename(urlparse(segment.url).path)\n                log.debug(\"Waiting for segment: {fname} ({wait:.01f}s)\".format(fname=fname, wait=time_to_wait))\n                sleep_until(segment.available_at)\n\n            if segment.range:\n                start, length = segment.range\n                if length:\n                    end = start + length - 1\n                else:\n                    end = \"\"\n                headers[\"Range\"] = \"bytes={0}-{1}\".format(start, end)\n\n            return self.session.http.get(segment.url,\n                                         timeout=self.timeout,\n                                         exception=StreamError,\n                                         headers=headers)\n        except StreamError as err:\n            log.error(\"Failed to open segment {0}: {1}\", segment.url, err)\n            return self.fetch(segment, retries - 1)\n\n    def write(self, segment, res, chunk_size=8192):\n        for chunk in res.iter_content(chunk_size):\n            if not self.closed:\n                self.reader.buffer.write(chunk)\n            else:\n                log.warning(\"Download of segment: {} aborted\".format(segment.url))\n                return\n\n        log.debug(\"Download of segment: {} complete\".format(segment.url))\n\n\nclass DASHStreamWorker(SegmentedStreamWorker):\n    def __init__(self, *args, **kwargs):\n        SegmentedStreamWorker.__init__(self, *args, **kwargs)\n        self.mpd = self.stream.mpd\n        self.period = self.stream.period\n\n    @staticmethod\n    def get_representation(mpd, representation_id, mime_type):\n        for aset in mpd.periods[0].adaptationSets:\n            for rep in aset.representations:\n                if rep.id == representation_id and rep.mimeType == mime_type:\n                    return rep\n\n    def iter_segments(self):\n        init = True\n        back_off_factor = 1\n        while not self.closed:\n            # find the representation by ID\n            representation = self.get_representation(self.mpd, self.reader.representation_id, self.reader.mime_type)\n            refresh_wait = max(self.mpd.minimumUpdatePeriod.total_seconds(),\n                               self.mpd.periods[0].duration.total_seconds()) or 5\n            with sleeper(refresh_wait * back_off_factor):\n                if representation:\n                    for segment in representation.segments(init=init):\n                        if self.closed:\n                            break\n                        yield segment\n                        # log.debug(\"Adding segment {0} to queue\", segment.url)\n\n                    if self.mpd.type == \"dynamic\":\n                        if not self.reload():\n                            back_off_factor = max(back_off_factor * 1.3, 10.0)\n                        else:\n                            back_off_factor = 1\n                    else:\n                        return\n                    init = False\n\n    def reload(self):\n        if self.closed:\n            return\n\n        self.reader.buffer.wait_free()\n        log.debug(\"Reloading manifest ({0}:{1})\".format(self.reader.representation_id, self.reader.mime_type))\n        res = self.session.http.get(self.mpd.url, exception=StreamError)\n\n        new_mpd = MPD(self.session.http.xml(res, ignore_ns=True),\n                      base_url=self.mpd.base_url,\n                      url=self.mpd.url,\n                      timelines=self.mpd.timelines)\n\n        new_rep = self.get_representation(new_mpd, self.reader.representation_id, self.reader.mime_type)\n        with freeze_timeline(new_mpd):\n            changed = len(list(itertools.islice(new_rep.segments(), 1))) > 0\n\n        if changed:\n            self.mpd = new_mpd\n\n        return changed\n\n\nclass DASHStreamReader(SegmentedStreamReader):\n    __worker__ = DASHStreamWorker\n    __writer__ = DASHStreamWriter\n\n    def __init__(self, stream, representation_id, mime_type, *args, **kwargs):\n        SegmentedStreamReader.__init__(self, stream, *args, **kwargs)\n        self.mime_type = mime_type\n        self.representation_id = representation_id\n        log.debug(\"Opening DASH reader for: {0} ({1})\".format(self.representation_id, self.mime_type))\n\n\n\nclass DASHStream(Stream):\n    __shortname__ = \"dash\"\n\n    def __init__(self,\n                 session,\n                 mpd,\n                 video_representation=None,\n                 audio_representation=None,\n                 period=0,\n                 **args):\n        super(DASHStream, self).__init__(session)\n        self.mpd = mpd\n        self.video_representation = video_representation\n        self.audio_representation = audio_representation\n        self.period = period\n        self.args = args\n\n    def __json__(self):\n        req = requests.Request(method=\"GET\", url=self.mpd.url, **valid_args(self.args))\n        req = req.prepare()\n\n        headers = dict(map(normalize_key, req.headers.items()))\n        return dict(type=type(self).shortname(), url=req.url, headers=headers)\n\n    @classmethod\n    def parse_manifest(cls, session, url, **args):\n        \"\"\"\n        Attempt to parse a DASH manifest file and return its streams\n\n        :param session: Streamlink session instance\n        :param url: URL of the manifest file\n        :return: a dict of name -> DASHStream instances\n        \"\"\"\n        ret = {}\n        res = session.http.get(url, **args)\n        url = res.url\n\n        urlp = list(urlparse(url))\n        urlp[2], _ = urlp[2].rsplit(\"/\", 1)\n\n        mpd = MPD(session.http.xml(res, ignore_ns=True), base_url=urlunparse(urlp), url=url)\n\n        video, audio = [], []\n\n        # Search for suitable video and audio representations\n        for aset in mpd.periods[0].adaptationSets:\n            if aset.contentProtection:\n                raise PluginError(\"{} is protected by DRM\".format(url))\n            for rep in aset.representations:\n                if rep.mimeType.startswith(\"video\"):\n                    video.append(rep)\n                elif rep.mimeType.startswith(\"audio\"):\n                    audio.append(rep)\n\n        if not video:\n            video = [None]\n\n        if not audio:\n            audio = [None]\n\n        for vid, aud in itertools.product(video, audio):\n            stream = DASHStream(session, mpd, vid, aud, **args)\n            stream_name = []\n\n            if vid:\n                stream_name.append(\"{:0.0f}{}\".format(vid.height or vid.bandwidth, \"p\" if vid.height else \"k\"))\n            if audio and len(audio) > 1:\n                stream_name.append(\"a{:0.0f}k\".format(aud.bandwidth))\n            ret['+'.join(stream_name)] = stream\n        return ret\n\n    def open(self):\n        if self.video_representation:\n            video = DASHStreamReader(self, self.video_representation.id, self.video_representation.mimeType)\n            video.open()\n\n        if self.audio_representation:\n            audio = DASHStreamReader(self, self.audio_representation.id, self.audio_representation.mimeType)\n            audio.open()\n\n        if self.video_representation and self.audio_representation:\n            return FFMPEGMuxer(self.session, video, audio, copyts=True).open()\n        elif self.video_representation:\n            return video\n        elif self.audio_representation:\n            return audio\n\n    def to_url(self):\n        return self.mpd.url\n", "path": "src/streamlink/stream/dash.py"}], "after_files": [{"content": "import itertools\nimport logging\nimport datetime\nimport os.path\n\nimport requests\nfrom streamlink import StreamError, PluginError\nfrom streamlink.compat import urlparse, urlunparse\nfrom streamlink.stream.http import valid_args, normalize_key\nfrom streamlink.stream.stream import Stream\nfrom streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline\nfrom streamlink.stream.ffmpegmux import FFMPEGMuxer\nfrom streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter\nfrom streamlink.utils.l10n import Language\n\nlog = logging.getLogger(__name__)\n\n\nclass DASHStreamWriter(SegmentedStreamWriter):\n    def __init__(self, reader, *args, **kwargs):\n        options = reader.stream.session.options\n        kwargs[\"retries\"] = options.get(\"dash-segment-attempts\")\n        kwargs[\"threads\"] = options.get(\"dash-segment-threads\")\n        kwargs[\"timeout\"] = options.get(\"dash-segment-timeout\")\n        SegmentedStreamWriter.__init__(self, reader, *args, **kwargs)\n\n    def fetch(self, segment, retries=None):\n        if self.closed or not retries:\n            return\n\n        try:\n            headers = {}\n            now = datetime.datetime.now(tz=utc)\n            if segment.available_at > now:\n                time_to_wait = (segment.available_at - now).total_seconds()\n                fname = os.path.basename(urlparse(segment.url).path)\n                log.debug(\"Waiting for segment: {fname} ({wait:.01f}s)\".format(fname=fname, wait=time_to_wait))\n                sleep_until(segment.available_at)\n\n            if segment.range:\n                start, length = segment.range\n                if length:\n                    end = start + length - 1\n                else:\n                    end = \"\"\n                headers[\"Range\"] = \"bytes={0}-{1}\".format(start, end)\n\n            return self.session.http.get(segment.url,\n                                         timeout=self.timeout,\n                                         exception=StreamError,\n                                         headers=headers)\n        except StreamError as err:\n            log.error(\"Failed to open segment {0}: {1}\", segment.url, err)\n            return self.fetch(segment, retries - 1)\n\n    def write(self, segment, res, chunk_size=8192):\n        for chunk in res.iter_content(chunk_size):\n            if not self.closed:\n                self.reader.buffer.write(chunk)\n            else:\n                log.warning(\"Download of segment: {} aborted\".format(segment.url))\n                return\n\n        log.debug(\"Download of segment: {} complete\".format(segment.url))\n\n\nclass DASHStreamWorker(SegmentedStreamWorker):\n    def __init__(self, *args, **kwargs):\n        SegmentedStreamWorker.__init__(self, *args, **kwargs)\n        self.mpd = self.stream.mpd\n        self.period = self.stream.period\n\n    @staticmethod\n    def get_representation(mpd, representation_id, mime_type):\n        for aset in mpd.periods[0].adaptationSets:\n            for rep in aset.representations:\n                if rep.id == representation_id and rep.mimeType == mime_type:\n                    return rep\n\n    def iter_segments(self):\n        init = True\n        back_off_factor = 1\n        while not self.closed:\n            # find the representation by ID\n            representation = self.get_representation(self.mpd, self.reader.representation_id, self.reader.mime_type)\n            refresh_wait = max(self.mpd.minimumUpdatePeriod.total_seconds(),\n                               self.mpd.periods[0].duration.total_seconds()) or 5\n            with sleeper(refresh_wait * back_off_factor):\n                if representation:\n                    for segment in representation.segments(init=init):\n                        if self.closed:\n                            break\n                        yield segment\n                        # log.debug(\"Adding segment {0} to queue\", segment.url)\n\n                    if self.mpd.type == \"dynamic\":\n                        if not self.reload():\n                            back_off_factor = max(back_off_factor * 1.3, 10.0)\n                        else:\n                            back_off_factor = 1\n                    else:\n                        return\n                    init = False\n\n    def reload(self):\n        if self.closed:\n            return\n\n        self.reader.buffer.wait_free()\n        log.debug(\"Reloading manifest ({0}:{1})\".format(self.reader.representation_id, self.reader.mime_type))\n        res = self.session.http.get(self.mpd.url, exception=StreamError)\n\n        new_mpd = MPD(self.session.http.xml(res, ignore_ns=True),\n                      base_url=self.mpd.base_url,\n                      url=self.mpd.url,\n                      timelines=self.mpd.timelines)\n\n        new_rep = self.get_representation(new_mpd, self.reader.representation_id, self.reader.mime_type)\n        with freeze_timeline(new_mpd):\n            changed = len(list(itertools.islice(new_rep.segments(), 1))) > 0\n\n        if changed:\n            self.mpd = new_mpd\n\n        return changed\n\n\nclass DASHStreamReader(SegmentedStreamReader):\n    __worker__ = DASHStreamWorker\n    __writer__ = DASHStreamWriter\n\n    def __init__(self, stream, representation_id, mime_type, *args, **kwargs):\n        SegmentedStreamReader.__init__(self, stream, *args, **kwargs)\n        self.mime_type = mime_type\n        self.representation_id = representation_id\n        log.debug(\"Opening DASH reader for: {0} ({1})\".format(self.representation_id, self.mime_type))\n\n\n\nclass DASHStream(Stream):\n    __shortname__ = \"dash\"\n\n    def __init__(self,\n                 session,\n                 mpd,\n                 video_representation=None,\n                 audio_representation=None,\n                 period=0,\n                 **args):\n        super(DASHStream, self).__init__(session)\n        self.mpd = mpd\n        self.video_representation = video_representation\n        self.audio_representation = audio_representation\n        self.period = period\n        self.args = args\n\n    def __json__(self):\n        req = requests.Request(method=\"GET\", url=self.mpd.url, **valid_args(self.args))\n        req = req.prepare()\n\n        headers = dict(map(normalize_key, req.headers.items()))\n        return dict(type=type(self).shortname(), url=req.url, headers=headers)\n\n    @classmethod\n    def parse_manifest(cls, session, url, **args):\n        \"\"\"\n        Attempt to parse a DASH manifest file and return its streams\n\n        :param session: Streamlink session instance\n        :param url: URL of the manifest file\n        :return: a dict of name -> DASHStream instances\n        \"\"\"\n        ret = {}\n        res = session.http.get(url, **args)\n        url = res.url\n\n        urlp = list(urlparse(url))\n        urlp[2], _ = urlp[2].rsplit(\"/\", 1)\n\n        mpd = MPD(session.http.xml(res, ignore_ns=True), base_url=urlunparse(urlp), url=url)\n\n        video, audio = [], []\n\n        # Search for suitable video and audio representations\n        for aset in mpd.periods[0].adaptationSets:\n            if aset.contentProtection:\n                raise PluginError(\"{} is protected by DRM\".format(url))\n            for rep in aset.representations:\n                if rep.mimeType.startswith(\"video\"):\n                    video.append(rep)\n                elif rep.mimeType.startswith(\"audio\"):\n                    audio.append(rep)\n\n        if not video:\n            video = [None]\n\n        if not audio:\n            audio = [None]\n\n        locale = session.localization\n        locale_lang = locale.language\n        lang = None\n        available_languages = set()\n\n        # if the locale is explicitly set, prefer that language over others\n        for aud in audio:\n            if aud and aud.lang:\n                available_languages.add(aud.lang)\n                try:\n                    if locale.explicit and aud.lang and Language.get(aud.lang) == locale_lang:\n                        lang = aud.lang\n                except LookupError:\n                    continue\n\n        if not lang:\n            # filter by the first language that appears\n            lang = audio[0] and audio[0].lang\n\n        log.debug(\"Available languages for DASH audio streams: {0} (using: {1})\".format(\", \".join(available_languages) or \"NONE\", lang or \"n/a\"))\n\n        # if the language is given by the stream, filter out other languages that do not match\n        if len(available_languages) > 1:\n            audio = list(filter(lambda a: a.lang is None or a.lang == lang, audio))\n\n        for vid, aud in itertools.product(video, audio):\n            stream = DASHStream(session, mpd, vid, aud, **args)\n            stream_name = []\n\n            if vid:\n                stream_name.append(\"{:0.0f}{}\".format(vid.height or vid.bandwidth, \"p\" if vid.height else \"k\"))\n            if audio and len(audio) > 1:\n                stream_name.append(\"a{:0.0f}k\".format(aud.bandwidth))\n            ret['+'.join(stream_name)] = stream\n        return ret\n\n    def open(self):\n        if self.video_representation:\n            video = DASHStreamReader(self, self.video_representation.id, self.video_representation.mimeType)\n            video.open()\n\n        if self.audio_representation:\n            audio = DASHStreamReader(self, self.audio_representation.id, self.audio_representation.mimeType)\n            audio.open()\n\n        if self.video_representation and self.audio_representation:\n            return FFMPEGMuxer(self.session, video, audio, copyts=True).open()\n        elif self.video_representation:\n            return video\n        elif self.audio_representation:\n            return audio\n\n    def to_url(self):\n        return self.mpd.url\n", "path": "src/streamlink/stream/dash.py"}]}