Repository: streamlink/streamlink

Issue:
DASHStream: can't use headers for DASHStreamReader/Worker/Writer
## Bug Report

<!-- Replace [ ] with [x] in order to check the box -->
- [x] This is a bug report and I have read the contribution guidelines.

### Description

could be related to https://github.com/streamlink/streamlink/issues/2270
but there is no `403 Client Error` so lets use a different issue here

**headers** (User-Agent) used with **DASHStream.parse_manifest** will only work here

https://github.com/streamlink/streamlink/blob/5db462eb993d659e27d9de4babd14ab33eb4ad8d/src/streamlink/stream/dash.py#L174

https://github.com/streamlink/streamlink/blob/5db462eb993d659e27d9de4babd14ab33eb4ad8d/src/streamlink/stream/dash.py#L226

not in DASHStreamReader/Worker/Writer

### Expected / Actual behavior

allow headers in **reload** ... , same as hls

HLS with request_params

https://github.com/streamlink/streamlink/blob/62409cdb1537626dbeca35606ed5d913090e6878/src/streamlink/stream/hls.py#L282-L288

https://github.com/streamlink/streamlink/blob/62409cdb1537626dbeca35606ed5d913090e6878/src/streamlink/stream/hls.py#L193

DASH does not have request_params

https://github.com/streamlink/streamlink/blob/62409cdb1537626dbeca35606ed5d913090e6878/src/streamlink/stream/dash.py#L128-L136

https://github.com/streamlink/streamlink/blob/62409cdb1537626dbeca35606ed5d913090e6878/src/streamlink/stream/dash.py#L111

### Reproduction steps / Explicit stream URLs to test

1. `streamlink https://www.tf1.fr/lci/direct 576p_dash`

https://github.com/streamlink/streamlink/blob/62409cdb1537626dbeca35606ed5d913090e6878/src/streamlink/plugins/tf1.py#L49

### Log output

```
$ streamlink https://www.tf1.fr/lci/direct 576p_dash
[cli][debug] OS:         Linux
[cli][debug] Python:     3.6.7
[cli][debug] Streamlink: 1.0.0+3.g4100688.dirty
[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.54.0)
[cli][info] Found matching plugin tf1 for URL https://www.tf1.fr/lci/direct
[plugin.tf1][debug] Found channel lci
[plugin.tf1][debug] Got dash stream https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=
[utils.l10n][debug] Language code: en_US
[stream.dash][debug] Available languages for DASH audio streams: NONE (using: n/a)
[plugin.tf1][debug] Got hls stream https://lci-hls-live-ssl.tf1.fr/lci/1/hls/master_4000000.m3u8?e=&st=
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: 234p_dash, 360p_dash, 576p_dash, 234p (worst), 360p, 576p_alt, 576p, 720p (best)
[cli][info] Opening stream: 576p_dash (dash)
[stream.dash][debug] Opening DASH reader for: live_1828_H264 (video/mp4)
[stream.dash_manifest][debug] Generating segment timeline for dynamic playlist (id=live_1828_H264))
[stream.dash][debug] Reloading manifest (live_1828_H264:video/mp4)
[stream.dash][debug] Opening DASH reader for: live_2328_AAC (audio/mp4)
[stream.dash_manifest][debug] Generating segment timeline for dynamic playlist (id=live_2328_AAC))
[stream.dash][debug] Reloading manifest (live_2328_AAC:audio/mp4)
[stream.mp4mux-ffmpeg][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/ffmpeg-2811-460 -i /tmp/ffmpeg-2811-532 -c:v copy -c:a copy -copyts -f matroska pipe:1
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-2811-460
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-2811-532
[cli][debug] Pre-buffering 8192 bytes
[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_1828/init.m4v complete
Exception in thread Thread-DASHStreamWorker:
Traceback (most recent call last):
  File "src/streamlink/plugin/api/http_session.py", line 166, in request
    res.raise_for_status()
  File "env/lib/python3.6/site-packages/requests/models.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "src/streamlink/stream/segmented.py", line 59, in run
    for segment in self.iter_segments():
  File "src/streamlink/stream/dash.py", line 97, in iter_segments
    if not self.reload():
  File "src/streamlink/stream/dash.py", line 111, in reload
    res = self.session.http.get(self.mpd.url, exception=StreamError)
  File "env/lib/python3.6/site-packages/requests/sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "src/streamlink/plugin/api/http_session.py", line 175, in request
    raise err
streamlink.exceptions.StreamError: Unable to open URL: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=
(403 Client Error: Forbidden for url: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=)
Exception in thread Thread-DASHStreamWorker:
Traceback (most recent call last):
  File "src/streamlink/plugin/api/http_session.py", line 166, in request
    res.raise_for_status()
  File "env/lib/python3.6/site-packages/requests/models.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "src/streamlink/stream/segmented.py", line 59, in run
    for segment in self.iter_segments():
  File "src/streamlink/stream/dash.py", line 97, in iter_segments
    if not self.reload():
  File "src/streamlink/stream/dash.py", line 111, in reload
    res = self.session.http.get(self.mpd.url, exception=StreamError)
  File "env/lib/python3.6/site-packages/requests/sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "src/streamlink/plugin/api/http_session.py", line 175, in request
    raise err
streamlink.exceptions.StreamError: Unable to open URL: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=
(403 Client Error: Forbidden for url: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2000000.mpd?e=&st=)

[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2328/init.m4a complete
[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2328/65796000.m4a complete
[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2328/65800000.m4a complete
[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_2328/65804000.m4a complete
[stream.dash][debug] Download of segment: https://lci-das-live-ssl.tf1.fr/lci/1/dash/live_1828/65792000.m4v complete
```


### Additional comments, screenshots, etc.

used with a global User-Agent fixed it



Discussion:
You need to update the session headers, either in the tf1 plugin or dash.py:
`self.session.http.headers.update (...)`
otherwise the headers only work in the corresponding `http.get ()` call.

Resolving patch:
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
@@ -1,3 +1,4 @@
+import copy
 import itertools
 import logging
 import datetime
@@ -30,7 +31,8 @@ def fetch(self, segment, retries=None):
             return
 
         try:
-            headers = {}
+            request_args = copy.deepcopy(self.reader.stream.args)
+            headers = request_args.pop("headers", {})
             now = datetime.datetime.now(tz=utc)
             if segment.available_at > now:
                 time_to_wait = (segment.available_at - now).total_seconds()
@@ -49,7 +51,8 @@ def fetch(self, segment, retries=None):
             return self.session.http.get(segment.url,
                                          timeout=self.timeout,
                                          exception=StreamError,
-                                         headers=headers)
+                                         headers=headers,
+                                         **request_args)
         except StreamError as err:
             log.error("Failed to open segment {0}: {1}", segment.url, err)
             return self.fetch(segment, retries - 1)
@@ -109,7 +112,7 @@ def reload(self):
 
         self.reader.buffer.wait_free()
         log.debug("Reloading manifest ({0}:{1})".format(self.reader.representation_id, self.reader.mime_type))
-        res = self.session.http.get(self.mpd.url, exception=StreamError)
+        res = self.session.http.get(self.mpd.url, exception=StreamError, **self.stream.args)
 
         new_mpd = MPD(self.session.http.xml(res, ignore_ns=True),
                       base_url=self.mpd.base_url,


Test patch: