Repository: streamlink/streamlink

Issue:
Test is flaky: TestTwitchHLSStream.test_hls_disable_ads_has_midstream
## Bug Report

<!-- Replace the space character between the square brackets with an x in order to check the boxes -->
- [x] This is a bug report and I have read the contribution guidelines.
- [x] I am using the latest development version from the master branch.


### Description

<!-- Explain the bug as thoroughly as you can. Don't leave out information which is necessary for us to reproduce and debug this issue. -->

While doing reproducibility tests on the Debian package, the test `TestTwitchHLSStream.test_hls_disable_ads_has_midstream` sometimes fails.
I don't think this related to specific changes in the Debian package so this is why I'm posting this here.
On my computer, the failure is relatively rare (1/100+).

```
_____________ TestTwitchHLSStream.test_hls_disable_ads_has_preroll _____________

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_preroll>
mock_log = <MagicMock name='log' id='139738930315216'>

    @patch("streamlink.plugins.twitch.log")
    def test_hls_disable_ads_has_preroll(self, mock_log):
        daterange = TagDateRangeAd(duration=4)
        thread, segments = self.subject([
            Playlist(0, [daterange, Segment(0), Segment(1)]),
            Playlist(2, [daterange, Segment(2), Segment(3)]),
            Playlist(4, [Segment(4), Segment(5)], end=True)
        ], disable_ads=True, low_latency=False)
    
>       self.assertEqual(
            self.await_read(read_all=True),
            self.content(segments, cond=lambda s: s.num >= 4),
            "Filters out preroll ad segments"
        )
E       AssertionError: b'' != b'[4][5]' : Filters out preroll ad segments

tests/plugins/test_twitch.py:124: AssertionError
```


### Expected / Actual behavior

<!-- What do you expect to happen, and what is actually happening? -->
The test TestTwitchHLSStream.test_hls_disable_ads_has_midstream should not fail.

### Reproduction steps / Explicit stream URLs to test

<!-- How can we reproduce this? Please note the exact steps below using the list format supplied. If you need more steps please add them. -->

To reproduce that failure, I've done this and it failed on the 118th iteration:
```bash
while PYTHONPATH=src pytest-3 tests/plugins/test_twitch.py; do true; done
```


### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a bug report!
Use the `--loglevel debug` parameter and avoid using parameters which suppress log output.
https://streamlink.github.io/cli.html#cmdoption-l

Make sure to **remove usernames and passwords**
You can copy the output to https://gist.github.com/ or paste it below.

Don't post screenshots of the log output and instead copy the text from your terminal application.
-->
This failure was found while doing reproducibility tests on the Debian package.
Full log available here: https://tests.reproducible-builds.org/debian/rb-pkg/unstable/amd64/streamlink.html

```
============================= test session starts ==============================
platform linux -- Python 3.9.1, pytest-4.6.11, py-1.9.0, pluggy-0.13.0
rootdir: /build/2/streamlink-2.0.0/2nd
plugins: requests-mock-1.7.0
collected 941 items

tests/test_api_http_session.py ...                                       [  0%]
tests/test_api_validate.py ..........................                    [  3%]
tests/test_buffer.py ............                                        [  4%]
tests/test_cache.py ...............                                      [  5%]
tests/test_cli_main.py ...............                                   [  7%]
tests/test_cli_playerout.py ..ss.s                                       [  8%]
tests/test_cli_util_progress.py ..                                       [  8%]
tests/test_cmdline.py .....ssssss                                        [  9%]
tests/test_cmdline_title.py ......ssssssss                               [ 11%]
tests/test_console.py .............                                      [ 12%]
tests/test_lazy_formatter.py ........                                    [ 13%]
tests/test_localization.py sssssssssssssss...............                [ 16%]
tests/test_log.py .........                                              [ 17%]
tests/test_meta.py ..                                                    [ 17%]
tests/test_options.py ...............                                    [ 19%]
tests/test_output.py .......................                             [ 21%]
tests/test_plugin.py ........                                            [ 22%]
tests/test_plugin_utils.py ......                                        [ 23%]
tests/test_plugins.py .................................................. [ 28%]
........................................................................ [ 36%]
....................................................                     [ 41%]
tests/test_plugins_input.py .....                                        [ 42%]
tests/test_plugins_meta.py .....                                         [ 42%]
tests/test_session.py ..................                                 [ 44%]
tests/test_stream_file.py ..                                             [ 44%]
tests/test_stream_json.py ......                                         [ 45%]
tests/test_streamlink_api.py .....                                       [ 46%]
tests/test_utils.py ..................                                   [ 47%]
tests/test_utils_args.py ...........                                     [ 49%]
tests/test_utils_crypto.py ..                                            [ 49%]
tests/test_utils_times.py ..                                             [ 49%]
tests/test_utils_url.py ....                                             [ 49%]
tests/plugins/test_abematv.py .                                          [ 50%]
tests/plugins/test_abweb.py ..                                           [ 50%]
tests/plugins/test_adultswim.py .                                        [ 50%]
tests/plugins/test_afreeca.py .                                          [ 50%]
tests/plugins/test_albavision.py .........                               [ 51%]
tests/plugins/test_animelab.py ..                                        [ 51%]
tests/plugins/test_app17.py ..                                           [ 51%]
tests/plugins/test_ard_live.py ..                                        [ 52%]
tests/plugins/test_ard_mediathek.py ..                                   [ 52%]
tests/plugins/test_artetv.py .                                           [ 52%]
tests/plugins/test_atresplayer.py ..                                     [ 52%]
tests/plugins/test_bbciplayer.py ..                                      [ 52%]
tests/plugins/test_bfmtv.py .                                            [ 52%]
tests/plugins/test_bigo.py ..                                            [ 53%]
tests/plugins/test_bilibili.py ..                                        [ 53%]
tests/plugins/test_bloomberg.py .                                        [ 53%]
tests/plugins/test_brightcove.py ..                                      [ 53%]
tests/plugins/test_btsports.py .                                         [ 53%]
tests/plugins/test_btv.py .                                              [ 53%]
tests/plugins/test_canalplus.py .                                        [ 53%]
tests/plugins/test_cbsnews.py ......                                     [ 54%]
tests/plugins/test_cdnbg.py ..                                           [ 54%]
tests/plugins/test_ceskatelevize.py ..                                   [ 55%]
tests/plugins/test_cinergroup.py ..                                      [ 55%]
tests/plugins/test_clubbingtv.py .                                       [ 55%]
tests/plugins/test_cnews.py .                                            [ 55%]
tests/plugins/test_crunchyroll.py .                                      [ 55%]
tests/plugins/test_dailymotion.py .                                      [ 55%]
tests/plugins/test_dash.py ......                                        [ 56%]
tests/plugins/test_delfi.py .                                            [ 56%]
tests/plugins/test_deutschewelle.py .                                    [ 56%]
tests/plugins/test_dlive.py .                                            [ 56%]
tests/plugins/test_dogan.py ..                                           [ 56%]
tests/plugins/test_dogus.py ..                                           [ 57%]
tests/plugins/test_dommune.py ..                                         [ 57%]
tests/plugins/test_drdk.py ..                                            [ 57%]
tests/plugins/test_earthcam.py ..                                        [ 57%]
tests/plugins/test_egame.py ..                                           [ 57%]
tests/plugins/test_eltrecetv.py .                                        [ 58%]
tests/plugins/test_euronews.py .                                         [ 58%]
tests/plugins/test_facebook.py .                                         [ 58%]
tests/plugins/test_filmon.py ...........                                 [ 59%]
tests/plugins/test_foxtr.py ..                                           [ 59%]
tests/plugins/test_funimationnow.py ..                                   [ 59%]
tests/plugins/test_galatasaraytv.py ..                                   [ 60%]
tests/plugins/test_gardenersworld.py ..                                  [ 60%]
tests/plugins/test_garena.py ...                                         [ 60%]
tests/plugins/test_goltelevision.py .                                    [ 60%]
tests/plugins/test_goodgame.py ..                                        [ 60%]
tests/plugins/test_googledrive.py ..                                     [ 61%]
tests/plugins/test_gulli.py .                                            [ 61%]
tests/plugins/test_hitbox.py ..                                          [ 61%]
tests/plugins/test_huajiao.py ..                                         [ 61%]
tests/plugins/test_huomao.py ..                                          [ 61%]
tests/plugins/test_huya.py ..                                            [ 62%]
tests/plugins/test_idf1.py .                                             [ 62%]
tests/plugins/test_ine.py ..                                             [ 62%]
tests/plugins/test_invintus.py .                                         [ 62%]
tests/plugins/test_kugou.py ..                                           [ 62%]
tests/plugins/test_latina.py ..                                          [ 62%]
tests/plugins/test_linelive.py ..                                        [ 63%]
tests/plugins/test_live_russia_tv.py .                                   [ 63%]
tests/plugins/test_liveedu.py ..                                         [ 63%]
tests/plugins/test_liveme.py .                                           [ 63%]
tests/plugins/test_livestream.py ..                                      [ 63%]
tests/plugins/test_lrt.py ..                                             [ 63%]
tests/plugins/test_ltv_lsm_lv.py ..                                      [ 64%]
tests/plugins/test_mediaklikk.py ..                                      [ 64%]
tests/plugins/test_mico.py ..                                            [ 64%]
tests/plugins/test_mitele.py .                                           [ 64%]
tests/plugins/test_mjunoon.py ...                                        [ 65%]
tests/plugins/test_mrtmk.py ..                                           [ 65%]
tests/plugins/test_n13tv.py ..                                           [ 65%]
tests/plugins/test_nbc.py ..                                             [ 65%]
tests/plugins/test_nbcnews.py ..                                         [ 65%]
tests/plugins/test_nbcsports.py ..                                       [ 66%]
tests/plugins/test_nhkworld.py ..                                        [ 66%]
tests/plugins/test_nicolive.py ..                                        [ 66%]
tests/plugins/test_nos.py ..                                             [ 66%]
tests/plugins/test_nownews.py .......                                    [ 67%]
tests/plugins/test_nrk.py ..                                             [ 67%]
tests/plugins/test_ntv.py ..                                             [ 67%]
tests/plugins/test_okru.py ..                                            [ 68%]
tests/plugins/test_olympicchannel.py .                                   [ 68%]
tests/plugins/test_oneplusone.py ..                                      [ 68%]
tests/plugins/test_onetv.py ...                                          [ 68%]
tests/plugins/test_openrectv.py ..                                       [ 68%]
tests/plugins/test_orf_tvthek.py ..                                      [ 69%]
tests/plugins/test_periscope.py ..                                       [ 69%]
tests/plugins/test_picarto.py ..                                         [ 69%]
tests/plugins/test_piczel.py ..                                          [ 69%]
tests/plugins/test_pixiv.py .                                            [ 69%]
tests/plugins/test_playtv.py .                                           [ 70%]
tests/plugins/test_pluto.py ..                                           [ 70%]
tests/plugins/test_pluzz.py .                                            [ 70%]
tests/plugins/test_powerapp.py ..                                        [ 70%]
tests/plugins/test_qq.py ..                                              [ 70%]
tests/plugins/test_radiko.py ..                                          [ 70%]
tests/plugins/test_radionet.py .                                         [ 71%]
tests/plugins/test_raiplay.py .                                          [ 71%]
tests/plugins/test_reuters.py .                                          [ 71%]
tests/plugins/test_rotana.py ..                                          [ 71%]
tests/plugins/test_rtbf.py .                                             [ 71%]
tests/plugins/test_rtlxl.py ..                                           [ 71%]
tests/plugins/test_rtpplay.py ..                                         [ 72%]
tests/plugins/test_rtve.py ..                                            [ 72%]
tests/plugins/test_rtvs.py ..                                            [ 72%]
tests/plugins/test_ruv.py .                                              [ 72%]
tests/plugins/test_sbscokr.py .                                          [ 72%]
tests/plugins/test_schoolism.py .....                                    [ 73%]
tests/plugins/test_senategov.py ..                                       [ 73%]
tests/plugins/test_showroom.py ..                                        [ 73%]
tests/plugins/test_sportal.py .                                          [ 73%]
tests/plugins/test_sportschau.py ..                                      [ 73%]
tests/plugins/test_ssh101.py ..                                          [ 74%]
tests/plugins/test_stadium.py .                                          [ 74%]
tests/plugins/test_steam.py ..                                           [ 74%]
tests/plugins/test_stream.py ........                                    [ 75%]
tests/plugins/test_streamable.py ..                                      [ 75%]
tests/plugins/test_streamingvideoprovider.py ..                          [ 75%]
tests/plugins/test_streann.py ..                                         [ 75%]
tests/plugins/test_stv.py ..                                             [ 76%]
tests/plugins/test_svtplay.py ..                                         [ 76%]
tests/plugins/test_swisstxt.py .                                         [ 76%]
tests/plugins/test_teamliquid.py .                                       [ 76%]
tests/plugins/test_teleclubzoom.py ..                                    [ 76%]
tests/plugins/test_telefe.py .                                           [ 76%]
tests/plugins/test_tf1.py ..                                             [ 77%]
tests/plugins/test_tga.py ..                                             [ 77%]
tests/plugins/test_theplatform.py ..                                     [ 77%]
tests/plugins/test_tigerdile.py .                                        [ 77%]
tests/plugins/test_tlctr.py .                                            [ 77%]
tests/plugins/test_turkuvaz.py ..                                        [ 78%]
tests/plugins/test_tv360.py ..                                           [ 78%]
tests/plugins/test_tv3cat.py ..                                          [ 78%]
tests/plugins/test_tv4play.py ..                                         [ 78%]
tests/plugins/test_tv5monde.py .                                         [ 78%]
tests/plugins/test_tv8.py ..                                             [ 78%]
tests/plugins/test_tv999.py ..                                           [ 79%]
tests/plugins/test_tvibo.py ..                                           [ 79%]
tests/plugins/test_tvp.py .                                              [ 79%]
tests/plugins/test_tvplayer.py ....                                      [ 79%]
tests/plugins/test_tvrby.py ..                                           [ 80%]
tests/plugins/test_tvrplus.py .                                          [ 80%]
tests/plugins/test_tvtoya.py ..                                          [ 80%]
tests/plugins/test_twitcasting.py ..                                     [ 80%]
tests/plugins/test_twitch.py .......F.......................             [ 83%]
tests/plugins/test_ustreamtv.py ...                                      [ 84%]
tests/plugins/test_ustvnow.py ....                                       [ 84%]
tests/plugins/test_viasat.py ..                                          [ 84%]
tests/plugins/test_vidio.py .                                            [ 85%]
tests/plugins/test_vimeo.py .                                            [ 85%]
tests/plugins/test_vinhlongtv.py .                                       [ 85%]
tests/plugins/test_viutv.py ...                                          [ 85%]
tests/plugins/test_vk.py ..                                              [ 85%]
tests/plugins/test_vlive.py ....                                         [ 86%]
tests/plugins/test_vrtbe.py .                                            [ 86%]
tests/plugins/test_vtvgo.py .                                            [ 86%]
tests/plugins/test_wasd.py ..                                            [ 86%]
tests/plugins/test_webcast_india_gov.py .                                [ 86%]
tests/plugins/test_webtv.py .                                            [ 86%]
tests/plugins/test_welt.py ..                                            [ 87%]
tests/plugins/test_wwenetwork.py ..                                      [ 87%]
tests/plugins/test_youtube.py ....                                       [ 87%]
tests/plugins/test_yupptv.py ..                                          [ 87%]
tests/plugins/test_zattoo.py ..                                          [ 88%]
tests/plugins/test_zdf_mediathek.py ..                                   [ 88%]
tests/plugins/test_zeenews.py ..                                         [ 88%]
tests/plugins/test_zengatv.py .                                          [ 88%]
tests/plugins/test_zhanqi.py .                                           [ 88%]
tests/streams/test_dash.py .................                             [ 90%]
tests/streams/test_dash_parser.py ...................                    [ 92%]
tests/streams/test_ffmpegmux.py .......................                  [ 95%]
tests/streams/test_hls.py .................                              [ 96%]
tests/streams/test_hls_filtered.py ......                                [ 97%]
tests/streams/test_hls_playlist.py ..                                    [ 97%]
tests/streams/test_stream_streamprocess.py .............                 [ 99%]
tests/streams/test_stream_to_url.py ........                             [ 99%]
tests/streams/test_stream_wrappers.py .                                  [100%]

=================================== FAILURES ===================================
_____________ TestTwitchHLSStream.test_hls_disable_ads_has_preroll _____________

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_preroll>
mock_log = <MagicMock name='log' id='139738930315216'>

    @patch("streamlink.plugins.twitch.log")
    def test_hls_disable_ads_has_preroll(self, mock_log):
        daterange = TagDateRangeAd(duration=4)
        thread, segments = self.subject([
            Playlist(0, [daterange, Segment(0), Segment(1)]),
            Playlist(2, [daterange, Segment(2), Segment(3)]),
            Playlist(4, [Segment(4), Segment(5)], end=True)
        ], disable_ads=True, low_latency=False)
    
>       self.assertEqual(
            self.await_read(read_all=True),
            self.content(segments, cond=lambda s: s.num >= 4),
            "Filters out preroll ad segments"
        )
E       AssertionError: b'' != b'[4][5]' : Filters out preroll ad segments

tests/plugins/test_twitch.py:124: AssertionError
----------------------------- Captured stdout call -----------------------------
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 0 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Adding segment 1 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing worker thread
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][info] Filtering out segments and pausing stream output
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][debug] Download of segment 4 complete
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][info] Resuming stream output
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.hls][debug] Download of segment 5 complete
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
[stream.segmented][debug] Closing writer thread
------------------------------ Captured log call -------------------------------
DEBUG    stream.hls:hls.py:229 Reloading playlist
DEBUG    stream.hls:hls.py:214 First Sequence: 0; Last Sequence: 1
DEBUG    stream.hls:hls.py:216 Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
DEBUG    stream.hls:hls.py:314 Adding segment 0 to queue
DEBUG    stream.hls:hls.py:314 Adding segment 1 to queue
DEBUG    stream.hls:hls.py:229 Reloading playlist
DEBUG    stream.hls:hls.py:314 Adding segment 2 to queue
DEBUG    stream.hls:hls.py:314 Adding segment 3 to queue
DEBUG    stream.hls:hls.py:229 Reloading playlist
DEBUG    stream.hls:hls.py:314 Adding segment 4 to queue
DEBUG    stream.hls:hls.py:314 Adding segment 5 to queue
DEBUG    stream.segmented:segmented.py:60 Closing worker thread
INFO     stream.hls:hls.py:144 Filtering out segments and pausing stream output
DEBUG    stream.hls:hls.py:176 Download of segment 4 complete
INFO     stream.hls:hls.py:139 Resuming stream output
DEBUG    stream.hls:hls.py:176 Download of segment 5 complete
DEBUG    stream.segmented:segmented.py:127 Closing writer thread
============== 1 failed, 908 passed, 32 skipped in 124.81 seconds ==============
```


Details with ` --show-capture=all --showlocals --full-trace -v` options:
```
________________________________________________________ TestTwitchHLSStream.test_hls_disable_ads_has_midstream ________________________________________________________

self = <unittest.case._Outcome object at 0x7fbd651f4bb0>, test_case = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>
isTest = True

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, isTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

exc_info   = None
isTest     = True
old_success = True
self       = <unittest.case._Outcome object at 0x7fbd651f4bb0>
test_case  = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>

/usr/lib/python3.9/unittest/case.py:59:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, result = <TestCaseFunction test_hls_disable_ads_has_midstream>

    def run(self, result=None):
        orig_result = result
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

        result.startTest(self)

        testMethod = getattr(self, self._testMethodName)
        if (getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)):
            # If the class or method was skipped.
            try:
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                self._addSkip(result, self, skip_why)
            finally:
                result.stopTest(self)
            return
        expecting_failure_method = getattr(testMethod,
                                           "__unittest_expecting_failure__", False)
        expecting_failure_class = getattr(self,
                                          "__unittest_expecting_failure__", False)
        expecting_failure = expecting_failure_class or expecting_failure_method
        outcome = _Outcome(result)
        try:
            self._outcome = outcome

            with outcome.testPartExecutor(self):
                self._callSetUp()
            if outcome.success:
                outcome.expecting_failure = expecting_failure
                with outcome.testPartExecutor(self, isTest=True):
>                   self._callTestMethod(testMethod)

expecting_failure = False
expecting_failure_class = False
expecting_failure_method = False
orig_result = <TestCaseFunction test_hls_disable_ads_has_midstream>
outcome    = <unittest.case._Outcome object at 0x7fbd651f4bb0>
result     = <TestCaseFunction test_hls_disable_ads_has_midstream>
self       = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>
testMethod = <bound method TestTwitchHLSStream.test_hls_disable_ads_has_midstream of <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>>

/usr/lib/python3.9/unittest/case.py:593:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>
method = <bound method TestTwitchHLSStream.test_hls_disable_ads_has_midstream of <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>>

    def _callTestMethod(self, method):
>       method()

method     = <bound method TestTwitchHLSStream.test_hls_disable_ads_has_midstream of <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>>
self       = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>

/usr/lib/python3.9/unittest/case.py:550:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (<tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>,), keywargs = {}
newargs = (<tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, <MagicMock name='log' id='140451425333600'>), newkeywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
        with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):
>           return func(*newargs, **newkeywargs)

args       = (<tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>,)
func       = <function TestTwitchHLSStream.test_hls_disable_ads_has_midstream at 0x7fbd6565dc10>
keywargs   = {}
newargs    = (<tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, <MagicMock name='log' id='140451425333600'>)
newkeywargs = {}
patched    = <function TestTwitchHLSStream.test_hls_disable_ads_has_midstream at 0x7fbd6565dca0>
self       = <unittest.mock._patch object at 0x7fbd65661b20>

/usr/lib/python3.9/unittest/mock.py:1337:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, mock_log = <MagicMock name='log' id='140451425333600'>

    @patch("streamlink.plugins.twitch.log")
    def test_hls_disable_ads_has_midstream(self, mock_log):
        daterange = TagDateRangeAd(start=DATETIME_BASE + timedelta(seconds=2), duration=2)
        thread, segments = self.subject([
            Playlist(0, [Segment(0), Segment(1)]),
            Playlist(2, [daterange, Segment(2), Segment(3)]),
            Playlist(4, [Segment(4), Segment(5)], end=True)
        ], disable_ads=True, low_latency=False)

>       self.assertEqual(
            self.await_read(read_all=True),
            self.content(segments, cond=lambda s: s.num != 2 and s.num != 3),
            "Filters out mid-stream ad segments"
        )

daterange  = <tests.plugins.test_twitch.TagDateRangeAd object at 0x7fbd5ec4c370>
mock_log   = <MagicMock name='log' id='140451425333600'>
segments   = OrderedDict([(0, <tests.plugins.test_twitch.Segment object at 0x7fbd5ec4c3a0>), (1, <tests.plugins.test_twitch.Segment...ins.test_twitch.Segment object at 0x7fbd5ec4c9d0>), (5, <tests.plugins.test_twitch.Segment object at 0x7fbd5ec4ca30>)])
self       = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>
thread     = <HLSStreamReadThread(Thread-5, stopped daemon 140451336156928)>

tests/plugins/test_twitch.py:144:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, first = b'', second = b'[0][1][4][5]'
msg = 'Filters out mid-stream ad segments'

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

assertion_func = <bound method TestCase._baseAssertEqual of <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>>
first      = b''
msg        = 'Filters out mid-stream ad segments'
second     = b'[0][1][4][5]'
self       = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>

/usr/lib/python3.9/unittest/case.py:831:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>, first = b'', second = b'[0][1][4][5]'
msg = "b'' != b'[0][1][4][5]' : Filters out mid-stream ad segments"

    def _baseAssertEqual(self, first, second, msg=None):
        """The default assertEqual implementation, not type specific."""
        if not first == second:
            standardMsg = '%s != %s' % _common_shorten_repr(first, second)
            msg = self._formatMessage(msg, standardMsg)
>           raise self.failureException(msg)
E           AssertionError: b'' != b'[0][1][4][5]' : Filters out mid-stream ad segments

first      = b''
msg        = "b'' != b'[0][1][4][5]' : Filters out mid-stream ad segments"
second     = b'[0][1][4][5]'
self       = <tests.plugins.test_twitch.TestTwitchHLSStream testMethod=test_hls_disable_ads_has_midstream>
standardMsg = "b'' != b'[0][1][4][5]'"

/usr/lib/python3.9/unittest/case.py:824: AssertionError
================================================================= 1 failed, 30 passed in 18.29 seconds =================================================================
```

### Additional comments, etc.

[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)


Discussion:

Patch:
diff --git a/docs/api_guide.rst b/docs/api_guide.rst
index 7afc9f5b..77d14f98 100644
--- a/docs/api_guide.rst
+++ b/docs/api_guide.rst
@@ -97,18 +97,3 @@ or set options like this:
 
 
 See :func:`Streamlink.set_option` to see which options are available.
-
-
-Examples
---------
-
-Simple player
-^^^^^^^^^^^^^
-
-This example uses the `PyGObject`_ module to playback a stream using the
-`GStreamer`_ framework.
-
-.. _PyGObject: https://wiki.gnome.org/action/show/Projects/PyGObject
-.. _GStreamer: https://gstreamer.freedesktop.org/
-
-.. literalinclude:: ../examples/gst-player.py
diff --git a/examples/gst-player.py b/examples/gst-player.py
deleted file mode 100755
index 79a5fdd6..00000000
--- a/examples/gst-player.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-
-import sys
-
-import gi
-
-from gi.repository import GObject as gobject, Gst as gst
-from streamlink import Streamlink, StreamError, PluginError, NoPluginError
-
-
-def exit(msg):
-    print(msg, file=sys.stderr)
-    sys.exit()
-
-
-class StreamlinkPlayer(object):
-    def __init__(self):
-        self.fd = None
-        self.mainloop = gobject.MainLoop()
-
-        # This creates a playbin pipeline and using the appsrc source
-        # we can feed it our stream data
-        self.pipeline = gst.ElementFactory.make("playbin", None)
-        self.pipeline.set_property("uri", "appsrc://")
-
-        # When the playbin creates the appsrc source it will call
-        # this callback and allow us to configure it
-        self.pipeline.connect("source-setup", self.on_source_setup)
-
-        # Creates a bus and set callbacks to receive errors
-        self.bus = self.pipeline.get_bus()
-        self.bus.add_signal_watch()
-        self.bus.connect("message::eos", self.on_eos)
-        self.bus.connect("message::error", self.on_error)
-
-    def exit(self, msg):
-        self.stop()
-        exit(msg)
-
-    def stop(self):
-        # Stop playback and exit mainloop
-        self.pipeline.set_state(gst.State.NULL)
-        self.mainloop.quit()
-
-        # Close the stream
-        if self.fd:
-            self.fd.close()
-
-    def play(self, stream):
-        # Attempt to open the stream
-        try:
-            self.fd = stream.open()
-        except StreamError as err:
-            self.exit("Failed to open stream: {0}".format(err))
-
-        # Start playback
-        self.pipeline.set_state(gst.State.PLAYING)
-        self.mainloop.run()
-
-    def on_source_setup(self, element, source):
-        # When this callback is called the appsrc expects
-        # us to feed it more data
-        source.connect("need-data", self.on_source_need_data)
-
-    def on_source_need_data(self, source, length):
-        # Attempt to read data from the stream
-        try:
-            data = self.fd.read(length)
-        except IOError as err:
-            self.exit("Failed to read data from stream: {0}".format(err))
-
-        # If data is empty it's the end of stream
-        if not data:
-            source.emit("end-of-stream")
-            return
-
-        # Convert the Python bytes into a GStreamer Buffer
-        # and then push it to the appsrc
-        buf = gst.Buffer.new_wrapped(data)
-        source.emit("push-buffer", buf)
-
-    def on_eos(self, bus, msg):
-        # Stop playback on end of stream
-        self.stop()
-
-    def on_error(self, bus, msg):
-        # Print error message and exit on error
-        error = msg.parse_error()[1]
-        self.exit(error)
-
-
-def main():
-    if len(sys.argv) < 3:
-        exit("Usage: {0} <url> <quality>".format(sys.argv[0]))
-
-    # Initialize and check GStreamer version
-    gi.require_version("Gst", "1.0")
-    gobject.threads_init()
-    gst.init(None)
-
-    # Collect arguments
-    url = sys.argv[1]
-    quality = sys.argv[2]
-
-    # Create the Streamlink session
-    streamlink = Streamlink()
-
-    # Enable logging
-    streamlink.set_loglevel("info")
-    streamlink.set_logoutput(sys.stdout)
-
-    # Attempt to fetch streams
-    try:
-        streams = streamlink.streams(url)
-    except NoPluginError:
-        exit("Streamlink is unable to handle the URL '{0}'".format(url))
-    except PluginError as err:
-        exit("Plugin error: {0}".format(err))
-
-    if not streams:
-        exit("No streams found on URL '{0}'".format(url))
-
-    # Look for specified stream
-    if quality not in streams:
-        exit("Unable to find '{0}' stream on URL '{1}'".format(quality, url))
-
-    # We found the stream
-    stream = streams[quality]
-
-    # Create the player and start playback
-    player = StreamlinkPlayer()
-
-    # Blocks until playback is done
-    player.play(stream)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/src/streamlink/plugins/afreeca.py b/src/streamlink/plugins/afreeca.py
index 76461000..185bff64 100644
--- a/src/streamlink/plugins/afreeca.py
+++ b/src/streamlink/plugins/afreeca.py
@@ -19,10 +19,7 @@ class AfreecaHLSStreamReader(HLSStreamReader):
 
 
 class AfreecaHLSStream(HLSStream):
-    def open(self):
-        reader = AfreecaHLSStreamReader(self)
-        reader.open()
-        return reader
+    __reader__ = AfreecaHLSStreamReader
 
 
 class AfreecaTV(Plugin):
diff --git a/src/streamlink/plugins/filmon.py b/src/streamlink/plugins/filmon.py
index f61d8e68..a7e37abb 100644
--- a/src/streamlink/plugins/filmon.py
+++ b/src/streamlink/plugins/filmon.py
@@ -72,6 +72,7 @@ class FilmOnHLSStreamReader(HLSStreamReader):
 
 class FilmOnHLS(HLSStream):
     __shortname__ = "hls-filmon"
+    __reader__ = FilmOnHLSStreamReader
 
     def __init__(self, session_, channel=None, vod_id=None, quality="high", **args):
         super().__init__(session_, None, **args)
@@ -116,12 +117,6 @@ class FilmOnHLS(HLSStream):
             raise TypeError("Stream has expired and cannot be converted to a URL")
         return url
 
-    def open(self):
-        reader = FilmOnHLSStreamReader(self)
-        reader.open()
-
-        return reader
-
 
 class FilmOnAPI:
     def __init__(self, session):
diff --git a/src/streamlink/plugins/showroom.py b/src/streamlink/plugins/showroom.py
index 9da07b56..795f19a5 100644
--- a/src/streamlink/plugins/showroom.py
+++ b/src/streamlink/plugins/showroom.py
@@ -91,11 +91,7 @@ class ShowroomHLSStreamReader(HLSStreamReader):
 
 
 class ShowroomHLSStream(HLSStream):
-    def open(self):
-        reader = ShowroomHLSStreamReader(self)
-        reader.open()
-
-        return reader
+    __reader__ = ShowroomHLSStreamReader
 
 
 class Showroom(Plugin):
diff --git a/src/streamlink/plugins/twitch.py b/src/streamlink/plugins/twitch.py
index c08a3054..f09df13b 100644
--- a/src/streamlink/plugins/twitch.py
+++ b/src/streamlink/plugins/twitch.py
@@ -119,6 +119,8 @@ class TwitchHLSStreamReader(HLSStreamReader):
 
 
 class TwitchHLSStream(HLSStream):
+    __reader__ = TwitchHLSStreamReader
+
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.disable_ads = self.session.get_plugin_option("twitch", "disable-ads")
@@ -133,10 +135,7 @@ class TwitchHLSStream(HLSStream):
             self.session.options.set("hls-segment-stream-data", True)
             log.info("Low latency streaming (HLS live edge: {0})".format(live_edge))
 
-        reader = TwitchHLSStreamReader(self)
-        reader.open()
-
-        return reader
+        return super().open()
 
     @classmethod
     def _get_variant_playlist(cls, res):
diff --git a/src/streamlink/stream/hls.py b/src/streamlink/stream/hls.py
index b6aac66a..bfea5295 100644
--- a/src/streamlink/stream/hls.py
+++ b/src/streamlink/stream/hls.py
@@ -405,6 +405,7 @@ class HLSStream(HTTPStream):
     """
 
     __shortname__ = "hls"
+    __reader__ = HLSStreamReader
 
     def __init__(self, session_, url, url_master=None, force_restart=False, start_offset=0, duration=None, **args):
         HTTPStream.__init__(self, session_, url, **args)
@@ -432,7 +433,7 @@ class HLSStream(HTTPStream):
         return self.url_master
 
     def open(self):
-        reader = HLSStreamReader(self)
+        reader = self.__reader__(self)
         reader.open()
 
         return reader


Test patch:
diff --git a/tests/mixins/stream_hls.py b/tests/mixins/stream_hls.py
index ac634252..8d7b2fb2 100644
--- a/tests/mixins/stream_hls.py
+++ b/tests/mixins/stream_hls.py
@@ -7,7 +7,7 @@ from threading import Event, Thread
 import requests_mock
 
 from streamlink import Streamlink
-from streamlink.stream.hls import HLSStream
+from streamlink.stream.hls import HLSStream, HLSStreamWriter as _HLSStreamWriter
 
 
 class HLSItemBase:
@@ -82,6 +82,26 @@ class Segment(HLSItemBase):
         )
 
 
+class EventedHLSStreamWriter(_HLSStreamWriter):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.write_wait = Event()
+        self.write_done = Event()
+
+    def write(self, *args, **kwargs):
+        # only write once per step
+        self.write_wait.wait()
+        self.write_wait.clear()
+
+        try:
+            # don't write again during teardown
+            if not self.closed:
+                super().write(*args, **kwargs)
+        finally:
+            # notify main thread that writing has finished
+            self.write_done.set()
+
+
 class HLSStreamReadThread(Thread):
     """
     Run the reader on a separate thread, so that each read can be controlled from within the main thread
@@ -116,10 +136,11 @@ class HLSStreamReadThread(Thread):
 
     def run(self):
         while not self.reader.buffer.closed:
+            # at least one read was attempted
+            self.read_once.set()
             # only read once per step
             self.read_wait.wait()
             self.read_wait.clear()
-            self.read_once.set()
 
             try:
                 # don't read again during teardown
@@ -184,12 +205,22 @@ class TestMixinStreamHLS(unittest.TestCase):
     # close read thread and make sure that all threads have terminated before moving on
     def close_thread(self):
         thread = self.thread
+        if isinstance(thread.reader.writer, EventedHLSStreamWriter):
+            thread.reader.writer.write_wait.set()
         thread.reader.close()
         thread.read_wait.set()
         thread.reader.writer.join()
         thread.reader.worker.join()
         thread.join()
 
+    # make one write call on the write thread and wait until it has finished
+    def await_write(self, write_calls=1, timeout=5):
+        writer = self.thread.reader.writer
+        for _ in range(write_calls):
+            writer.write_wait.set()
+            writer.write_done.wait(timeout)
+            writer.write_done.clear()
+
     # make one read call on the read thread and wait until it has finished
     def await_read(self, read_all=False, timeout=5):
         thread = self.thread
diff --git a/tests/plugins/test_twitch.py b/tests/plugins/test_twitch.py
index eb199178..b7e434ef 100644
--- a/tests/plugins/test_twitch.py
+++ b/tests/plugins/test_twitch.py
@@ -6,8 +6,8 @@ import requests_mock
 
 from streamlink import Streamlink
 from streamlink.plugin import PluginError
-from streamlink.plugins.twitch import Twitch, TwitchHLSStream
-from tests.mixins.stream_hls import Playlist, Segment as _Segment, Tag, TestMixinStreamHLS
+from streamlink.plugins.twitch import Twitch, TwitchHLSStream, TwitchHLSStreamReader, TwitchHLSStreamWriter
+from tests.mixins.stream_hls import EventedHLSStreamWriter, Playlist, Segment as _Segment, Tag, TestMixinStreamHLS
 
 
 DATETIME_BASE = datetime(2000, 1, 1, 0, 0, 0, 0)
@@ -44,6 +44,18 @@ class SegmentPrefetch(Segment):
         return "#EXT-X-TWITCH-PREFETCH:{0}".format(self.url(namespace))
 
 
+class _TwitchHLSStreamWriter(EventedHLSStreamWriter, TwitchHLSStreamWriter):
+    pass
+
+
+class _TwitchHLSStreamReader(TwitchHLSStreamReader):
+    __writer__ = _TwitchHLSStreamWriter
+
+
+class _TwitchHLSStream(TwitchHLSStream):
+    __reader__ = _TwitchHLSStreamReader
+
+
 class TestPluginTwitch(unittest.TestCase):
     def test_can_handle_url(self):
         should_match = [
@@ -66,7 +78,7 @@ class TestPluginTwitch(unittest.TestCase):
 
 @patch("streamlink.stream.hls.HLSStreamWorker.wait", MagicMock(return_value=True))
 class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
-    __stream__ = TwitchHLSStream
+    __stream__ = _TwitchHLSStream
 
     def get_session(self, options=None, disable_ads=False, low_latency=False):
         session = super().get_session(options)
@@ -82,6 +94,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(0, [daterange, Segment(0), Segment(1)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(2)
         self.assertEqual(self.await_read(read_all=True), self.content(segments), "Doesn't filter out segments")
         self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
 
@@ -91,6 +104,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(0, [daterange, Segment(0), Segment(1)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(2)
         self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
         self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
 
@@ -100,6 +114,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(0, [daterange, Segment(0), Segment(1)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(2)
         self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
         self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
 
@@ -109,6 +124,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(0, [daterange, Segment(0), Segment(1)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(2)
         self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
         self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
 
@@ -121,6 +137,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(4, [Segment(4), Segment(5)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(6)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments, cond=lambda s: s.num >= 4),
@@ -141,6 +158,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(4, [Segment(4), Segment(5)], end=True)
         ], disable_ads=True, low_latency=False)
 
+        self.await_write(6)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments, cond=lambda s: s.num != 2 and s.num != 3),
@@ -159,6 +177,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(2, [Segment(2), Segment(3)], end=True)
         ], disable_ads=False, low_latency=False)
 
+        self.await_write(4)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments),
@@ -177,6 +196,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
         self.assertEqual(2, self.session.options.get("hls-live-edge"))
         self.assertEqual(True, self.session.options.get("hls-segment-stream-data"))
 
+        self.await_write(6)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments, cond=lambda s: s.num >= 4),
@@ -198,6 +218,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
         self.assertEqual(4, self.session.options.get("hls-live-edge"))
         self.assertEqual(False, self.session.options.get("hls-segment-stream-data"))
 
+        self.await_write(8)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments, cond=lambda s: s.num < 8),
@@ -217,6 +238,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
         self.assertTrue(self.session.get_plugin_option("twitch", "low-latency"))
         self.assertFalse(self.session.get_plugin_option("twitch", "disable-ads"))
 
+        self.await_write(6)
         self.await_read(read_all=True)
         self.assertEqual(mock_log.info.mock_calls, [
             call("Low latency streaming (HLS live edge: 2)"),
@@ -231,6 +253,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7), SegmentPrefetch(8), SegmentPrefetch(9)], end=True)
         ], disable_ads=False, low_latency=True)
 
+        self.await_write(8)
         self.assertEqual(
             self.await_read(read_all=True),
             self.content(segments, cond=lambda s: s.num > 1),
@@ -250,6 +273,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7), SegmentPrefetch(8), SegmentPrefetch(9)], end=True)
         ], disable_ads=True, low_latency=True)
 
+        self.await_write(8)
         self.await_read(read_all=True)
         self.assertEqual(mock_log.info.mock_calls, [
             call("Will skip ad segments"),
@@ -265,6 +289,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7)], end=True)
         ], disable_ads=True, low_latency=True)
 
+        self.await_write(6)
         self.await_read(read_all=True)
         self.assertEqual(mock_log.info.mock_calls, [
             call("Will skip ad segments"),
diff --git a/tests/streams/test_hls_filtered.py b/tests/streams/test_hls_filtered.py
index e80186d5..73b487db 100644
--- a/tests/streams/test_hls_filtered.py
+++ b/tests/streams/test_hls_filtered.py
@@ -2,8 +2,8 @@ import unittest
 from threading import Event
 from unittest.mock import MagicMock, call, patch
 
-from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter
-from tests.mixins.stream_hls import Playlist, Segment, TestMixinStreamHLS
+from streamlink.stream.hls import HLSStream, HLSStreamReader
+from tests.mixins.stream_hls import EventedHLSStreamWriter, Playlist, Segment, TestMixinStreamHLS
 
 
 FILTERED = "filtered"
@@ -15,36 +15,12 @@ class SegmentFiltered(Segment):
         self.title = FILTERED
 
 
-class _TestSubjectHLSStreamWriter(HLSStreamWriter):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.write_wait = Event()
-        self.write_done = Event()
-
-    def write(self, *args, **kwargs):
-        # only write once per step
-        self.write_wait.wait()
-        self.write_wait.clear()
-
-        try:
-            # don't write again during teardown
-            if not self.closed:
-                super().write(*args, **kwargs)
-        finally:
-            # notify main thread that writing has finished
-            self.write_done.set()
-
-
 class _TestSubjectHLSReader(HLSStreamReader):
-    __writer__ = _TestSubjectHLSStreamWriter
+    __writer__ = EventedHLSStreamWriter
 
 
 class _TestSubjectHLSStream(HLSStream):
-    def open(self):
-        reader = _TestSubjectHLSReader(self)
-        reader.open()
-
-        return reader
+    __reader__ = _TestSubjectHLSReader
 
 
 @patch("streamlink.stream.hls.HLSStreamWorker.wait", MagicMock(return_value=True))
@@ -55,17 +31,6 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
     def filter_sequence(cls, sequence):
         return sequence.segment.title == FILTERED
 
-    def close_thread(self):
-        self.thread.reader.writer.write_wait.set()
-        super().close_thread()
-
-    # make one write call on the write thread and wait until it has finished
-    def await_write(self):
-        writer = self.thread.reader.writer
-        writer.write_wait.set()
-        writer.write_done.wait()
-        writer.write_done.clear()
-
     def get_session(self, options=None, *args, **kwargs):
         session = super().get_session(options)
         session.set_option("hls-live-edge", 2)
@@ -85,8 +50,7 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
             Playlist(0, [SegmentFiltered(0), SegmentFiltered(1)], end=True)
         ])
 
-        self.await_write()
-        self.await_write()
+        self.await_write(2)
         data = self.await_read()
         self.assertEqual(data, self.content(segments), "Does not filter by default")
 
@@ -104,14 +68,12 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
         self.assertTrue(reader.filter_event.is_set(), "Doesn't let the reader wait if not filtering")
 
         for i in range(2):
-            self.await_write()
-            self.await_write()
+            self.await_write(2)
             self.assertEqual(len(mock_log.info.mock_calls), i * 2 + 1)
             self.assertEqual(mock_log.info.mock_calls[i * 2 + 0], call("Filtering out segments and pausing stream output"))
             self.assertFalse(reader.filter_event.is_set(), "Lets the reader wait if filtering")
 
-            self.await_write()
-            self.await_write()
+            self.await_write(2)
             self.assertEqual(len(mock_log.info.mock_calls), i * 2 + 2)
             self.assertEqual(mock_log.info.mock_calls[i * 2 + 1], call("Resuming stream output"))
             self.assertTrue(reader.filter_event.is_set(), "Doesn't let the reader wait if not filtering")
@@ -150,8 +112,7 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
 
         self.assertTrue(reader.filter_event.is_set(), "Doesn't let the reader wait if not filtering")
 
-        self.await_write()
-        self.await_write()
+        self.await_write(2)
         self.assertFalse(reader.filter_event.is_set(), "Lets the reader wait if filtering")
 
         # make reader read (no data available yet)
@@ -214,7 +175,5 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
             "segment2",
         ]})
 
-        for _ in range(4):
-            self.await_write()
-
+        self.await_write(4)
         self.assertEqual(self.await_read(), self.content(segments, cond=lambda s: s.num % 2 > 0))