Esempio n. 1
0
    def test_offset_and_duration(self):
        thread, segments = self.subject([
            Playlist(1234, [
                Segment(0),
                Segment(1, duration=0.5),
                Segment(2, duration=0.5),
                Segment(3)
            ],
                     end=True)
        ],
                                        streamoptions={
                                            "start_offset": 1,
                                            "duration": 1
                                        })

        data = self.await_read(read_all=True)
        self.assertEqual(data,
                         self.content(segments, cond=lambda s: 0 < s.num < 3),
                         "Respects the offset and duration")
        self.assertTrue(
            all([self.called(s) for s in segments.values() if 0 < s.num < 3]),
            "Downloads second and third segment")
        self.assertFalse(
            any([self.called(s) for s in segments.values() if 0 > s.num > 3]),
            "Skips other segments")
Esempio n. 2
0
    def test_hls_low_latency_has_prefetch_disable_ads_has_preroll(
            self, mock_log):
        self.subject([
            Playlist(0,
                     [SegmentAd(0),
                      SegmentAd(1),
                      SegmentAd(2),
                      SegmentAd(3)]),
            Playlist(4, [
                Segment(4),
                Segment(5),
                Segment(6),
                Segment(7),
                SegmentPrefetch(8),
                SegmentPrefetch(9)
            ],
                     end=True)
        ],
                     disable_ads=True,
                     low_latency=True)

        self.await_read(read_all=True)
        self.assertEqual(mock_log.info.mock_calls, [
            call("Will skip ad segments"),
            call("Low latency streaming (HLS live edge: 2)"),
            call("Waiting for pre-roll ads to finish, be patient")
        ])
Esempio n. 3
0
    def test_filtered_no_timeout(self):
        thread, reader, writer, segments = self.subject([
            Playlist(0, [SegmentFiltered(0), SegmentFiltered(1)]),
            Playlist(2, [Segment(2), Segment(3)], end=True)
        ])

        self.assertTrue(reader.filter_event.is_set(), "Doesn't let the reader wait if not filtering")

        self.await_write(2)
        self.assertFalse(reader.filter_event.is_set(), "Lets the reader wait if filtering")

        # make reader read (no data available yet)
        thread.read_wait.set()
        # once data becomes available, the reader continues reading
        self.await_write()
        self.assertTrue(reader.filter_event.is_set(), "Reader is not waiting anymore")

        thread.read_done.wait()
        thread.read_done.clear()
        self.assertFalse(thread.error, "Doesn't time out when filtering")
        self.assertEqual(b"".join(thread.data), segments[2].content, "Reads next available buffer data")

        self.await_write()
        data = self.await_read()
        self.assertEqual(data, self.content(segments, cond=lambda s: s.num >= 2))
Esempio n. 4
0
    def test_hls_low_latency_has_prefetch_has_preroll(self, mock_log):
        thread, segments = self.subject([
            Playlist(0,
                     [SegmentAd(0),
                      SegmentAd(1),
                      SegmentAd(2),
                      SegmentAd(3)]),
            Playlist(4, [
                Segment(4),
                Segment(5),
                Segment(6),
                Segment(7),
                SegmentPrefetch(8),
                SegmentPrefetch(9)
            ],
                     end=True)
        ],
                                        disable_ads=False,
                                        low_latency=True)

        self.assertEqual(self.await_read(read_all=True),
                         self.content(segments, cond=lambda s: s.num > 1),
                         "Skips first two segments due to reduced live-edge")
        self.assertFalse(
            any([self.called(s) for s in segments.values() if s.num < 2]),
            "Skips first two preroll segments")
        self.assertTrue(
            all([self.called(s) for s in segments.values() if s.num >= 2]),
            "Downloads all remaining segments")
        self.assertEqual(mock_log.info.mock_calls,
                         [call("Low latency streaming (HLS live edge: 2)")])
Esempio n. 5
0
    def test_filtered_logging(self, mock_log):
        thread, reader, writer, segments = self.subject([
            Playlist(0, [SegmentFiltered(0), SegmentFiltered(1)]),
            Playlist(2, [Segment(2), Segment(3)]),
            Playlist(4, [SegmentFiltered(4), SegmentFiltered(5)]),
            Playlist(6, [Segment(6), Segment(7)], end=True)
        ])
        data = b""

        self.assertTrue(reader.filter_event.is_set(), "Doesn't let the reader wait if not filtering")

        for i in range(2):
            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(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")

            data += self.await_read()

        self.assertEqual(
            data,
            self.content(segments, cond=lambda s: s.num % 4 > 1),
            "Correctly filters out segments"
        )
        self.assertTrue(all([self.called(s) for s in segments.values()]), "Downloads all segments")
Esempio n. 6
0
    def test_offsets(self):
        map1 = TagMap(1, self.id(), {"BYTERANGE": "\"1234@0\""})
        map2 = TagMap(2, self.id(), {"BYTERANGE": "\"42@1337\""})
        self.mock("GET", self.url(map1), content=map1.content)
        self.mock("GET", self.url(map2), content=map2.content)
        s1, s2, s3, s4, s5 = Segment(0), Segment(1), Segment(2), Segment(3), Segment(4)

        self.subject([
            Playlist(0, [
                map1,
                Tag("EXT-X-BYTERANGE", "5@3"), s1,
                Tag("EXT-X-BYTERANGE", "7"), s2,
                map2,
                Tag("EXT-X-BYTERANGE", "11"), s3,
                Tag("EXT-X-BYTERANGE", "17@13"), s4,
                Tag("EXT-X-BYTERANGE", "19"), s5,
            ], end=True)
        ])

        self.await_write(5 * 2)
        self.await_read(read_all=True)
        self.assertEqual(self.mocks[self.url(map1)].last_request._request.headers["Range"], "bytes=0-1233")
        self.assertEqual(self.mocks[self.url(map2)].last_request._request.headers["Range"], "bytes=1337-1378")
        self.assertEqual(self.mocks[self.url(s1)].last_request._request.headers["Range"], "bytes=3-7")
        self.assertEqual(self.mocks[self.url(s2)].last_request._request.headers["Range"], "bytes=8-14")
        self.assertEqual(self.mocks[self.url(s3)].last_request._request.headers["Range"], "bytes=15-25")
        self.assertEqual(self.mocks[self.url(s4)].last_request._request.headers["Range"], "bytes=13-29")
        self.assertEqual(self.mocks[self.url(s5)].last_request._request.headers["Range"], "bytes=30-48")
Esempio n. 7
0
class TestHlsPlaylistReloadTime(TestMixinStreamHLS, unittest.TestCase):
    segments = [Segment(0, "", 11), Segment(1, "", 7), Segment(2, "", 5), Segment(3, "", 3)]

    def get_session(self, options=None, reload_time=None, *args, **kwargs):
        return super(TestHlsPlaylistReloadTime, self).get_session(
            dict(options or {}, **{"hls-live-edge": 3, "hls-playlist-reload-time": reload_time})
        )

    def subject(self, *args, **kwargs):
        thread, _ = super(TestHlsPlaylistReloadTime, self).subject(*args, **kwargs)
        self.await_read(read_all=True)

        return thread.reader.worker.playlist_reload_time

    def test_hls_playlist_reload_time_default(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="default")
        self.assertEqual(time, 4, "default sets the reload time to the playlist's target duration")

    def test_hls_playlist_reload_time_segment(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="segment")
        self.assertEqual(time, 3, "segment sets the reload time to the playlist's last segment")

    def test_hls_playlist_reload_time_segment_no_segments(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="segment")
        self.assertEqual(time, 4, "segment sets the reload time to the targetduration if no segments are available")

    def test_hls_playlist_reload_time_segment_no_segments_no_targetduration(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="segment")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")

    def test_hls_playlist_reload_time_live_edge(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="live-edge")
        self.assertEqual(time, 8, "live-edge sets the reload time to the sum of the number of segments of the live-edge")

    def test_hls_playlist_reload_time_live_edge_no_segments(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="live-edge")
        self.assertEqual(time, 4, "live-edge sets the reload time to the targetduration if no segments are available")

    def test_hls_playlist_reload_time_live_edge_no_segments_no_targetduration(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="live-edge")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")

    def test_hls_playlist_reload_time_number(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="2")
        self.assertEqual(time, 2, "number values override the reload time")

    def test_hls_playlist_reload_time_number_invalid(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="0")
        self.assertEqual(time, 4, "invalid number values set the reload time to the playlist's targetduration")

    def test_hls_playlist_reload_time_no_target_duration(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=0)], reload_time="default")
        self.assertEqual(time, 8, "uses the live-edge sum if the playlist is missing the targetduration data")

    def test_hls_playlist_reload_time_no_data(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="default")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no data is available")
Esempio n. 8
0
    def test_hls_segment_ignore_names(self):
        thread, reader, writer, segments = self.subject([
            Playlist(0, [Segment(0), Segment(1), Segment(2), Segment(3)], end=True)
        ], {"hls-segment-ignore-names": [
            ".*",
            "segment0",
            "segment2",
        ]})

        self.await_write(4)
        self.assertEqual(self.await_read(), self.content(segments, cond=lambda s: s.num % 2 > 0))
Esempio n. 9
0
    def test_hls_no_disable_ads_has_preroll(self, mock_log):
        thread, segments = self.subject([
            Playlist(0, [SegmentAd(0), SegmentAd(1)]),
            Playlist(2, [Segment(2), Segment(3)], end=True)
        ],
                                        disable_ads=False,
                                        low_latency=False)

        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")
        self.assertEqual(mock_log.info.mock_calls, [], "Doesn't log anything")
Esempio n. 10
0
    def test_map(self):
        discontinuity = Tag("EXT-X-DISCONTINUITY")
        map1 = TagMap(1, self.id())
        map2 = TagMap(2, self.id())
        self.mock("GET", self.url(map1), content=map1.content)
        self.mock("GET", self.url(map2), content=map2.content)

        thread, segments = self.subject([
            Playlist(
                0, [map1, Segment(0),
                    Segment(1),
                    Segment(2),
                    Segment(3)]),
            Playlist(4, [
                map1,
                Segment(4), map2,
                Segment(5),
                Segment(6), discontinuity,
                Segment(7)
            ],
                     end=True)
        ])

        data = self.await_read(read_all=True, timeout=None)
        self.assertEqual(
            data,
            self.content([
                map1, segments[1], map1, segments[2], map1, segments[3], map1,
                segments[4], map2, segments[5], map2, segments[6], segments[7]
            ]))
        self.assertTrue(self.called(map1, once=True),
                        "Downloads first map only once")
        self.assertTrue(self.called(map2, once=True),
                        "Downloads second map only once")
Esempio n. 11
0
    def test_filtered_timeout(self):
        thread, reader, writer, segments = self.subject([
            Playlist(0, [Segment(0), Segment(1)], end=True)
        ])

        self.await_write()
        data = self.await_read()
        self.assertEqual(data, segments[0].content, "Has read the first segment")

        # simulate a timeout by having an empty buffer
        # timeout value is set to 0
        with self.assertRaises(IOError) as cm:
            self.await_read()
        self.assertEqual(str(cm.exception), "Read timeout", "Raises a timeout error when no data is available to read")
Esempio n. 12
0
    def test_unknown_offset(self, mock_log: Mock):
        thread, _ = self.subject([
            Playlist(0, [
                Tag("EXT-X-BYTERANGE", "3"), Segment(0),
                Segment(1)
            ], end=True)
        ])

        self.await_write(2 - 1)
        self.thread.close()

        self.assertEqual(mock_log.error.call_args_list, [
            call("Failed to fetch segment 0: Missing BYTERANGE offset")
        ])
        self.assertFalse(self.called(Segment(0)))
Esempio n. 13
0
 def test_reload(self, mock_log):
     thread, segments = self.subject([
         Playlist(1, [Segment(0)]),
         self.InvalidPlaylist(),
         self.InvalidPlaylist(),
         Playlist(2, [Segment(2)], end=True)
     ])
     self.await_write(2)
     data = self.await_read(read_all=True)
     self.assertEqual(data, self.content(segments))
     self.close()
     self.await_close()
     self.assertEqual(mock_log.warning.mock_calls, [
         call("Failed to reload playlist: Missing #EXTM3U header"),
         call("Failed to reload playlist: Missing #EXTM3U header")
     ])
Esempio n. 14
0
    def test_hls_disable_ads_has_midstream(self, mock_log):
        thread, segments = self.subject([
            Playlist(0, [Segment(0), Segment(1)]),
            Playlist(2, [SegmentAd(2), SegmentAd(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")
        self.assertTrue(all([self.called(s) for s in segments.values()]),
                        "Downloads all segments")
        self.assertEqual(mock_log.info.mock_calls,
                         [call("Will skip ad segments")])
Esempio n. 15
0
    def test_hls_disable_ads_has_preroll(self, mock_log):
        thread, segments = self.subject([
            Playlist(0, [SegmentAd(0), SegmentAd(1)]),
            Playlist(2, [SegmentAd(2), SegmentAd(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")
        self.assertTrue(all([self.called(s) for s in segments.values()]),
                        "Downloads all segments")
        self.assertEqual(mock_log.info.mock_calls, [
            call("Will skip ad segments"),
            call("Waiting for pre-roll ads to finish, be patient")
        ])
Esempio n. 16
0
    def test_unknown_offset_map(self, mock_log: Mock):
        map1 = TagMap(1, self.id(), {"BYTERANGE": "\"1234\""})
        self.mock("GET", self.url(map1), content=map1.content)
        thread, _ = self.subject([
            Playlist(0, [
                Segment(0),
                map1,
                Segment(1)
            ], end=True)
        ])

        self.await_write(3 - 1)
        self.thread.close()

        self.assertEqual(mock_log.error.call_args_list, [
            call("Failed to fetch map for segment 1: Missing BYTERANGE offset")
        ])
        self.assertFalse(self.called(map1))
Esempio n. 17
0
    def test_hls_low_latency_no_prefetch(self, mock_log):
        self.subject([
            Playlist(
                0, [Segment(0), Segment(1),
                    Segment(2), Segment(3)]),
            Playlist(
                4, [Segment(4), Segment(5),
                    Segment(6), Segment(7)], end=True)
        ],
                     disable_ads=False,
                     low_latency=True)

        self.assertTrue(self.session.get_plugin_option("twitch",
                                                       "low-latency"))
        self.assertFalse(
            self.session.get_plugin_option("twitch", "disable-ads"))

        self.await_read(read_all=True)
        self.assertEqual(mock_log.info.mock_calls, [
            call("Low latency streaming (HLS live edge: 2)"),
            call("This is not a low latency stream")
        ])
Esempio n. 18
0
    def test_hls_low_latency_has_prefetch(self, mock_log):
        thread, segments = self.subject([
            Playlist(0, [
                Segment(0),
                Segment(1),
                Segment(2),
                Segment(3),
                SegmentPrefetch(4),
                SegmentPrefetch(5)
            ]),
            Playlist(4, [
                Segment(4),
                Segment(5),
                Segment(6),
                Segment(7),
                SegmentPrefetch(8),
                SegmentPrefetch(9)
            ],
                     end=True)
        ],
                                        disable_ads=False,
                                        low_latency=True)

        self.assertEqual(2, self.session.options.get("hls-live-edge"))
        self.assertEqual(True,
                         self.session.options.get("hls-segment-stream-data"))

        self.assertEqual(self.await_read(read_all=True),
                         self.content(segments, cond=lambda s: s.num >= 4),
                         "Skips first four segments due to reduced live-edge")
        self.assertFalse(
            any([self.called(s) for s in segments.values() if s.num < 4]),
            "Doesn't download old segments")
        self.assertTrue(
            all([self.called(s) for s in segments.values() if s.num >= 4]),
            "Downloads all remaining segments")
        self.assertEqual(mock_log.info.mock_calls,
                         [call("Low latency streaming (HLS live edge: 2)")])
Esempio n. 19
0
    def test_hls_no_low_latency_has_prefetch(self, mock_log):
        thread, segments = self.subject([
            Playlist(0, [
                Segment(0),
                Segment(1),
                Segment(2),
                Segment(3),
                SegmentPrefetch(4),
                SegmentPrefetch(5)
            ]),
            Playlist(4, [
                Segment(4),
                Segment(5),
                Segment(6),
                Segment(7),
                SegmentPrefetch(8),
                SegmentPrefetch(9)
            ],
                     end=True)
        ],
                                        disable_ads=False,
                                        low_latency=False)

        self.assertEqual(4, self.session.options.get("hls-live-edge"))
        self.assertEqual(False,
                         self.session.options.get("hls-segment-stream-data"))

        self.assertEqual(self.await_read(read_all=True),
                         self.content(segments, cond=lambda s: s.num < 8),
                         "Ignores prefetch segments")
        self.assertTrue(
            all([self.called(s) for s in segments.values() if s.num <= 7]),
            "Ignores prefetch segments")
        self.assertFalse(
            any([self.called(s) for s in segments.values() if s.num > 7]),
            "Ignores prefetch segments")
        self.assertEqual(mock_log.info.mock_calls, [], "Doesn't log anything")
Esempio n. 20
0
    def test_invalid_offset_reference(self, mock_log: Mock):
        thread, _ = self.subject([
            Playlist(0, [
                Tag("EXT-X-BYTERANGE", "3@0"), Segment(0),
                Segment(1),
                Tag("EXT-X-BYTERANGE", "5"), Segment(2),
                Segment(3)
            ], end=True)
        ])

        self.await_write(4 - 1)
        self.thread.close()

        self.assertEqual(mock_log.error.call_args_list, [
            call("Failed to fetch segment 2: Missing BYTERANGE offset")
        ])
        self.assertEqual(self.mocks[self.url(Segment(0))].last_request._request.headers["Range"], "bytes=0-2")
        self.assertFalse(self.called(Segment(2)))
Esempio n. 21
0
class TestHlsPlaylistReloadTime(TestMixinStreamHLS, unittest.TestCase):
    segments = [
        Segment(0, duration=11),
        Segment(1, duration=7),
        Segment(2, duration=5),
        Segment(3, duration=3)
    ]

    def get_session(self, options=None, reload_time=None, *args, **kwargs):
        return super().get_session(dict(options or {}, **{
            "hls-live-edge": 3,
            "hls-playlist-reload-time": reload_time
        }))

    def subject(self, *args, **kwargs):
        thread, segments = super().subject(start=False, *args, **kwargs)

        # mock the worker thread's _playlist_reload_time method, so that the main thread can wait on its call
        playlist_reload_time_called = Event()
        orig_playlist_reload_time = thread.reader.worker._playlist_reload_time

        def mocked_playlist_reload_time(*args, **kwargs):
            playlist_reload_time_called.set()
            return orig_playlist_reload_time(*args, **kwargs)

        # immediately kill the writer thread as we don't need it and don't want to wait for its queue polling to end
        def mocked_futures_get():
            return None, None

        with patch.object(thread.reader.worker, "_playlist_reload_time", side_effect=mocked_playlist_reload_time), \
             patch.object(thread.reader.writer, "_futures_get", side_effect=mocked_futures_get):
            self.start()

            if not playlist_reload_time_called.wait(timeout=5):  # pragma: no cover
                raise RuntimeError("Missing _playlist_reload_time() call")

            # wait for the worker thread to terminate, so that deterministic assertions can be done about the reload time
            thread.reader.worker.join()

            return thread.reader.worker.playlist_reload_time

    def test_hls_playlist_reload_time_default(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="default")
        self.assertEqual(time, 4, "default sets the reload time to the playlist's target duration")

    def test_hls_playlist_reload_time_segment(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="segment")
        self.assertEqual(time, 3, "segment sets the reload time to the playlist's last segment")

    def test_hls_playlist_reload_time_segment_no_segments(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="segment")
        self.assertEqual(time, 4, "segment sets the reload time to the targetduration if no segments are available")

    def test_hls_playlist_reload_time_segment_no_segments_no_targetduration(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="segment")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")

    def test_hls_playlist_reload_time_live_edge(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="live-edge")
        self.assertEqual(time, 8, "live-edge sets the reload time to the sum of the number of segments of the live-edge")

    def test_hls_playlist_reload_time_live_edge_no_segments(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="live-edge")
        self.assertEqual(time, 4, "live-edge sets the reload time to the targetduration if no segments are available")

    def test_hls_playlist_reload_time_live_edge_no_segments_no_targetduration(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="live-edge")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")

    def test_hls_playlist_reload_time_number(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="2")
        self.assertEqual(time, 2, "number values override the reload time")

    def test_hls_playlist_reload_time_number_invalid(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="0")
        self.assertEqual(time, 4, "invalid number values set the reload time to the playlist's targetduration")

    def test_hls_playlist_reload_time_no_target_duration(self):
        time = self.subject([Playlist(0, self.segments, end=True, targetduration=0)], reload_time="default")
        self.assertEqual(time, 8, "uses the live-edge sum if the playlist is missing the targetduration data")

    def test_hls_playlist_reload_time_no_data(self):
        time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="default")
        self.assertEqual(time, 6, "sets reload time to 6 seconds when no data is available")