async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): """Test rendering the hls playlist with 1 and 2 output segments.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) for i in range(2): segment = Segment(sequence=i, duration=SEGMENT_DURATION) hls.put(segment) await hass.async_block_till_done() hls_client = await hls_stream(stream) resp = await hls_client.get("/playlist.m3u8") assert resp.status == HTTPStatus.OK assert await resp.text() == make_playlist( sequence=0, segments=[make_segment(0), make_segment(1)]) segment = Segment(sequence=2, duration=SEGMENT_DURATION) hls.put(segment) await hass.async_block_till_done() resp = await hls_client.get("/playlist.m3u8") assert resp.status == HTTPStatus.OK assert await resp.text() == make_playlist( sequence=0, segments=[make_segment(0), make_segment(1), make_segment(2)]) stream_worker_sync.resume() stream.stop()
async def test_remove_incomplete_segment_on_exit(hass, stream_worker_sync): """Test that the incomplete segment gets removed when the worker thread quits.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() stream.start() hls = stream.add_provider(HLS_PROVIDER) segment = Segment(sequence=0, stream_id=0, duration=SEGMENT_DURATION) hls.put(segment) segment = Segment(sequence=1, stream_id=0, duration=SEGMENT_DURATION) hls.put(segment) segment = Segment(sequence=2, stream_id=0, duration=0) hls.put(segment) await hass.async_block_till_done() segments = hls._segments assert len(segments) == 3 assert not segments[-1].complete stream_worker_sync.resume() stream._thread_quit.set() stream._thread.join() stream._thread = None await hass.async_block_till_done() assert segments[-1].complete assert len(segments) == 2 stream.stop()
async def test_ll_hls_msn(hass, hls_stream, stream_worker_sync, hls_sync): """Test that requests using _HLS_msn get held and returned or rejected.""" await async_setup_component( hass, "stream", { "stream": { CONF_LL_HLS: True, CONF_SEGMENT_DURATION: SEGMENT_DURATION, CONF_PART_DURATION: TEST_PART_DURATION, } }, ) stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) hls_client = await hls_stream(stream) # Create 4 requests for sequences 0 through 3 # 0 and 1 should hold then go through and 2 and 3 should fail immediately. hls_sync.reset_request_pool(4) msn_requests = asyncio.gather( *(hls_client.get(f"/playlist.m3u8?_HLS_msn={i}") for i in range(4))) for sequence in range(3): await hls_sync.wait_for_handler() segment = Segment(sequence=sequence, duration=SEGMENT_DURATION) hls.put(segment) msn_responses = await msn_requests assert msn_responses[0].status == HTTPStatus.OK assert msn_responses[1].status == HTTPStatus.OK assert msn_responses[2].status == HTTPStatus.BAD_REQUEST assert msn_responses[3].status == HTTPStatus.BAD_REQUEST # Sequence number is now 2. Create six more requests for sequences 0 through 5. # Calls for msn 0 through 4 should work, 5 should fail. hls_sync.reset_request_pool(6) msn_requests = asyncio.gather( *(hls_client.get(f"/playlist.m3u8?_HLS_msn={i}") for i in range(6))) for sequence in range(3, 6): await hls_sync.wait_for_handler() segment = Segment(sequence=sequence, duration=SEGMENT_DURATION) hls.put(segment) msn_responses = await msn_requests assert msn_responses[0].status == HTTPStatus.OK assert msn_responses[1].status == HTTPStatus.OK assert msn_responses[2].status == HTTPStatus.OK assert msn_responses[3].status == HTTPStatus.OK assert msn_responses[4].status == HTTPStatus.OK assert msn_responses[5].status == HTTPStatus.BAD_REQUEST stream_worker_sync.resume()
async def test_hls_playlist_view_discontinuity(hass, setup_component, hls_stream, stream_worker_sync): """Test a discontinuity across segments in the stream with 3 segments.""" stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) segment = Segment(sequence=0, stream_id=0, duration=SEGMENT_DURATION) hls.put(segment) segment = Segment(sequence=1, stream_id=0, duration=SEGMENT_DURATION) hls.put(segment) segment = Segment( sequence=2, stream_id=1, duration=SEGMENT_DURATION, ) hls.put(segment) await hass.async_block_till_done() hls_client = await hls_stream(stream) resp = await hls_client.get("/playlist.m3u8") assert resp.status == HTTPStatus.OK assert await resp.text() == make_playlist( sequence=0, segments=[ make_segment(0), make_segment(1), make_segment(2, discontinuity=True), ], ) stream_worker_sync.resume() stream.stop()
async def test_recorder_discontinuity(tmpdir): """Test recorder save across a discontinuity.""" # Setup source = generate_h264_video() filename = f"{tmpdir}/test.mp4" # Run segment_1 = Segment(sequence=1, stream_id=0) add_parts_to_segment(segment_1, source) segment_1.duration = 4 segment_2 = Segment(sequence=2, stream_id=1) add_parts_to_segment(segment_2, source) segment_2.duration = 4 recorder_save_worker(filename, [segment_1, segment_2]) # Assert assert os.path.exists(filename)
async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sync): """Test a discontinuity with more segments than the segment deque can hold.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) hls_client = await hls_stream(stream) segment = Segment(sequence=0, stream_id=0, duration=SEGMENT_DURATION) hls.put(segment) # Produce enough segments to overfill the output buffer by one for sequence in range(MAX_SEGMENTS + 1): segment = Segment( sequence=sequence, stream_id=1, duration=SEGMENT_DURATION, ) hls.put(segment) await hass.async_block_till_done() resp = await hls_client.get("/playlist.m3u8") assert resp.status == HTTPStatus.OK # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist causing the # EXT-X-DISCONTINUITY tag to be omitted and EXT-X-DISCONTINUITY-SEQUENCE # returned instead. start = MAX_SEGMENTS + 1 - NUM_PLAYLIST_SEGMENTS segments = [] for sequence in range(start, MAX_SEGMENTS + 1): segments.append(make_segment(sequence)) assert await resp.text() == make_playlist( sequence=start, discontinuity_sequence=1, segments=segments, ) stream_worker_sync.resume() stream.stop()
async def test_recorder_save(tmpdir): """Test recorder save.""" # Setup source = generate_h264_video() filename = f"{tmpdir}/test.mp4" # Run segment = Segment(sequence=1) add_parts_to_segment(segment, source) segment.duration = 4 recorder_save_worker(filename, [segment]) # Assert assert os.path.exists(filename)
async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): """Test rendering the hls playlist with more segments than the segment deque can hold.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) hls_client = await hls_stream(stream) # Produce enough segments to overfill the output buffer by one for sequence in range(MAX_SEGMENTS + 1): segment = Segment(sequence=sequence, duration=SEGMENT_DURATION) hls.put(segment) await hass.async_block_till_done() resp = await hls_client.get("/playlist.m3u8") assert resp.status == HTTPStatus.OK # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist. start = MAX_SEGMENTS + 1 - NUM_PLAYLIST_SEGMENTS segments = [] for sequence in range(start, MAX_SEGMENTS + 1): segments.append(make_segment(sequence)) assert await resp.text() == make_playlist(sequence=start, segments=segments) # Fetch the actual segments with a fake byte payload for segment in hls.get_segments(): segment.init = INIT_BYTES segment.parts = [ Part( duration=SEGMENT_DURATION, has_keyframe=True, data=FAKE_PAYLOAD, ) ] # The segment that fell off the buffer is not accessible with patch.object(hls.stream_settings, "hls_part_timeout", 0.1): segment_response = await hls_client.get("/segment/0.m4s") assert segment_response.status == HTTPStatus.NOT_FOUND # However all segments in the buffer are accessible, even those that were not in the playlist. for sequence in range(1, MAX_SEGMENTS + 1): segment_response = await hls_client.get(f"/segment/{sequence}.m4s") assert segment_response.status == HTTPStatus.OK stream_worker_sync.resume() stream.stop()
def create_segment(sequence): """Create an empty segment.""" segment = Segment(sequence=sequence) segment.init = INIT_BYTES return segment