Skip to content

Commit 70c03e1

Browse files
committed
plugins.twitch: always download ad segments
- log when subsequent segments will be skipped/unskipped (instead of logging each skipped segment separately) - add test assertions for segment downloads and log output
1 parent 66b100c commit 70c03e1

File tree

2 files changed

+72
-29
lines changed

2 files changed

+72
-29
lines changed

src/streamlink/plugins/twitch.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,6 @@ def get_segment(self, uri):
172172

173173

174174
class TwitchHLSStreamWorker(HLSStreamWorker):
175-
scte35 = None
176-
177175
def _reload_playlist(self, text, url):
178176
return load_hls_playlist(text, url, parser=TwitchM3U8Parser)
179177

@@ -186,20 +184,23 @@ def __init__(self, *args, **kwargs):
186184
if self.disable_ads:
187185
log.info("Will skip ad segments")
188186

189-
def fetch(self, sequence, retries=None):
190-
if self.closed or not retries:
191-
return
192-
if sequence.segment.scte35 is not None:
193-
self.reader.worker.scte35 = sequence.segment.scte35
194-
if self.disable_ads and self.reader.worker.scte35 is True:
195-
log.debug("Skipping ad segment {0}".format(sequence.num))
196-
return
197-
return HLSStreamWriter.fetch(self, sequence, retries=retries)
187+
def write(self, sequence, *args, **kwargs):
188+
if self.disable_ads:
189+
if sequence.segment.scte35 is not None:
190+
self.reader.ads = sequence.segment.scte35
191+
if self.reader.ads:
192+
log.info("Will skip ads beginning with segment {0}".format(sequence.num))
193+
else:
194+
log.info("Will stop skipping ads beginning with segment {0}".format(sequence.num))
195+
if self.reader.ads:
196+
return
197+
return HLSStreamWriter.write(self, sequence, *args, **kwargs)
198198

199199

200200
class TwitchHLSStreamReader(HLSStreamReader):
201201
__worker__ = TwitchHLSStreamWorker
202202
__writer__ = TwitchHLSStreamWriter
203+
ads = None
203204

204205

205206
class TwitchHLSStream(HLSStream):

tests/plugins/test_twitch.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from streamlink.plugins.twitch import Twitch, TwitchHLSStream
66

77
import requests_mock
8+
from tests.mock import call, patch
89

910
from streamlink.session import Streamlink
1011
from tests.resources import text
@@ -78,61 +79,95 @@ def start_streamlink(self, kwargs=None):
7879
log.info("End of streamlink execution")
7980
return data
8081

82+
def mock(self, mocked, method, url, *args, **kwargs):
83+
mocked[url] = method(url, *args, **kwargs)
84+
8185
def get_result(self, streams, playlists):
86+
mocked = {}
8287
with requests_mock.Mocker() as mock:
83-
mock.get("http://mocked/path/master.m3u8", text=self.getMasterPlaylist())
84-
mock.get("http://mocked/path/playlist.m3u8", [{"text": playlist} for playlist in playlists])
88+
self.mock(mocked, mock.get, "http://mocked/path/master.m3u8", text=self.getMasterPlaylist())
89+
self.mock(mocked, mock.get, "http://mocked/path/playlist.m3u8", [{"text": p} for p in playlists])
8590
for i, stream in enumerate(streams):
86-
mock.get("http://mocked/path/stream{0}.ts".format(i), content=stream)
87-
return self.start_streamlink()
91+
self.mock(mocked, mock.get, "http://mocked/path/stream{0}.ts".format(i), content=stream)
92+
return self.start_streamlink(), mocked
8893

89-
def test_hls_scte35_start_with_end(self):
94+
@patch("streamlink.plugins.twitch.log")
95+
def test_hls_scte35_start_with_end(self, mock_logging):
9096
streams = ["[{0}]".format(i).encode("ascii") for i in range(12)]
9197
playlists = [
9298
self.getPlaylist(0, [self.scte35_out, 0, 1, 2, 3]),
9399
self.getPlaylist(4, [self.scte35_in, 4, 5, 6, 7]),
94100
self.getPlaylist(8, [8, 9, 10, 11]) + "#EXT-X-ENDLIST\n"
95101
]
96-
result = self.get_result(streams, playlists)
102+
result, mocked = self.get_result(streams, playlists)
97103

98104
expected = b''.join(streams[4:12])
99105
self.assertEqual(expected, result)
100-
101-
def test_hls_scte35_no_start(self):
106+
for i, _ in enumerate(streams):
107+
self.assertTrue(mocked["http://mocked/path/stream{0}.ts".format(i)].called)
108+
mock_logging.info.assert_has_calls([
109+
call("Will skip ad segments"),
110+
call("Will skip ads beginning with segment 0"),
111+
call("Will stop skipping ads beginning with segment 4")
112+
])
113+
114+
@patch("streamlink.plugins.twitch.log")
115+
def test_hls_scte35_no_start(self, mock_logging):
102116
streams = ["[{0}]".format(i).encode("ascii") for i in range(8)]
103117
playlists = [
104118
self.getPlaylist(0, [0, 1, 2, 3]),
105119
self.getPlaylist(4, [self.scte35_in, 4, 5, 6, 7]) + "#EXT-X-ENDLIST\n"
106120
]
107-
result = self.get_result(streams, playlists)
121+
result, mocked = self.get_result(streams, playlists)
108122

109123
expected = b''.join(streams[0:8])
110124
self.assertEqual(expected, result)
111-
112-
def test_hls_scte35_no_start_with_cont(self):
125+
for i, _ in enumerate(streams):
126+
self.assertTrue(mocked["http://mocked/path/stream{0}.ts".format(i)].called)
127+
mock_logging.info.assert_has_calls([
128+
call("Will skip ad segments")
129+
])
130+
131+
@patch("streamlink.plugins.twitch.log")
132+
def test_hls_scte35_no_start_with_cont(self, mock_logging):
113133
streams = ["[{0}]".format(i).encode("ascii") for i in range(8)]
114134
playlists = [
115135
self.getPlaylist(0, [self.scte35_out_cont, 0, 1, 2, 3]),
116136
self.getPlaylist(4, [self.scte35_in, 4, 5, 6, 7]) + "#EXT-X-ENDLIST\n"
117137
]
118-
result = self.get_result(streams, playlists)
138+
result, mocked = self.get_result(streams, playlists)
119139

120140
expected = b''.join(streams[4:8])
121141
self.assertEqual(expected, result)
122-
123-
def test_hls_scte35_no_end(self):
142+
for i, _ in enumerate(streams):
143+
self.assertTrue(mocked["http://mocked/path/stream{0}.ts".format(i)].called)
144+
mock_logging.info.assert_has_calls([
145+
call("Will skip ad segments"),
146+
call("Will skip ads beginning with segment 0"),
147+
call("Will stop skipping ads beginning with segment 4")
148+
])
149+
150+
@patch("streamlink.plugins.twitch.log")
151+
def test_hls_scte35_no_end(self, mock_logging):
124152
streams = ["[{0}]".format(i).encode("ascii") for i in range(12)]
125153
playlists = [
126154
self.getPlaylist(0, [0, 1, 2, 3]),
127155
self.getPlaylist(4, [self.scte35_out, 4, 5, 6, 7]),
128156
self.getPlaylist(8, [8, 9, 10, 11]) + "#EXT-X-ENDLIST\n"
129157
]
130-
result = self.get_result(streams, playlists)
158+
result, mocked = self.get_result(streams, playlists)
131159

132160
expected = b''.join(streams[0:4])
133161
self.assertEqual(expected, result)
134-
135-
def test_hls_scte35_in_between(self):
162+
for i, _ in enumerate(streams):
163+
self.assertTrue(mocked["http://mocked/path/stream{0}.ts".format(i)].called)
164+
mock_logging.info.assert_has_calls([
165+
call("Will skip ad segments"),
166+
call("Will skip ads beginning with segment 4")
167+
])
168+
169+
@patch("streamlink.plugins.twitch.log")
170+
def test_hls_scte35_in_between(self, mock_logging):
136171
streams = ["[{0}]".format(i).encode("ascii") for i in range(20)]
137172
playlists = [
138173
self.getPlaylist(0, [0, 1, 2, 3]),
@@ -141,7 +176,14 @@ def test_hls_scte35_in_between(self):
141176
self.getPlaylist(12, [12, 13, self.scte35_in, 14, 15]),
142177
self.getPlaylist(16, [16, 17, 18, 19]) + "#EXT-X-ENDLIST\n"
143178
]
144-
result = self.get_result(streams, playlists)
179+
result, mocked = self.get_result(streams, playlists)
145180

146181
expected = b''.join(streams[0:6]) + b''.join(streams[14:20])
147182
self.assertEqual(expected, result)
183+
for i, _ in enumerate(streams):
184+
self.assertTrue(mocked["http://mocked/path/stream{0}.ts".format(i)].called)
185+
mock_logging.info.assert_has_calls([
186+
call("Will skip ad segments"),
187+
call("Will skip ads beginning with segment 6"),
188+
call("Will stop skipping ads beginning with segment 14")
189+
])

0 commit comments

Comments
 (0)