Beispiel #1
0
async def test_record_stream(opp, opp_client, record_worker_sync):
    """
    Test record stream.

    Tests full integration with the stream component, and captures the
    stream worker and save worker to allow for clean shutdown of background
    threads.  The actual save logic is tested in test_recorder_save below.
    """
    await async_setup_component(opp, "stream", {"stream": {}})

    # Setup demo track
    source = generate_h264_video()
    stream = create_stream(opp, source)
    with patch.object(opp.config, "is_allowed_path", return_value=True):
        await stream.async_record("/example/path")

    # After stream decoding finishes, the record worker thread starts
    segments = await record_worker_sync.get_segments()
    assert len(segments) >= 1

    # Verify that the save worker was invoked, then block until its
    # thread completes and is shutdown completely to avoid thread leaks.
    await record_worker_sync.join()

    stream.stop()
Beispiel #2
0
async def test_recorder_timeout(opp, opp_client, stream_worker_sync):
    """
    Test recorder timeout.

    Mocks out the cleanup to assert that it is invoked after a timeout.
    This test does not start the recorder save thread.
    """
    await async_setup_component(opp, "stream", {"stream": {}})

    stream_worker_sync.pause()

    with patch(
            "openpeerpower.components.stream.IdleTimer.fire") as mock_timeout:
        # Setup demo track
        source = generate_h264_video()

        stream = create_stream(opp, source)
        with patch.object(opp.config, "is_allowed_path", return_value=True):
            await stream.async_record("/example/path")
        recorder = stream.add_provider("recorder")

        await recorder.recv()

        # Wait a minute
        future = dt_util.utcnow() + timedelta(minutes=1)
        async_fire_time_changed(opp, future)
        await opp.async_block_till_done()

        assert mock_timeout.called

        stream_worker_sync.resume()
        stream.stop()
        await opp.async_block_till_done()
        await opp.async_block_till_done()
Beispiel #3
0
async def test_hls_playlist_view_discontinuity(opp, hls_stream,
                                               stream_worker_sync):
    """Test a discontinuity across segments in the stream with 3 segments."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream = create_stream(opp, STREAM_SOURCE)
    stream_worker_sync.pause()
    hls = stream.add_provider("hls")

    hls.put(Segment(1, INIT_BYTES, MOOF_BYTES, DURATION, stream_id=0))
    hls.put(Segment(2, INIT_BYTES, MOOF_BYTES, DURATION, stream_id=0))
    hls.put(Segment(3, INIT_BYTES, MOOF_BYTES, DURATION, stream_id=1))
    await opp.async_block_till_done()

    hls_client = await hls_stream(stream)

    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 200
    assert await resp.text() == make_playlist(
        sequence=1,
        segments=[
            make_segment(1),
            make_segment(2),
            make_segment(3, discontinuity=True),
        ],
    )

    stream_worker_sync.resume()
    stream.stop()
Beispiel #4
0
async def test_hls_playlist_view(opp, hls_stream, stream_worker_sync):
    """Test rendering the hls playlist with 1 and 2 output segments."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream = create_stream(opp, STREAM_SOURCE)
    stream_worker_sync.pause()
    hls = stream.add_provider("hls")

    hls.put(Segment(1, INIT_BYTES, MOOF_BYTES, DURATION))
    await opp.async_block_till_done()

    hls_client = await hls_stream(stream)

    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 200
    assert await resp.text() == make_playlist(sequence=1,
                                              segments=[make_segment(1)])

    hls.put(Segment(2, INIT_BYTES, MOOF_BYTES, DURATION))
    await opp.async_block_till_done()
    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 200
    assert await resp.text() == make_playlist(
        sequence=1, segments=[make_segment(1),
                              make_segment(2)])

    stream_worker_sync.resume()
    stream.stop()
Beispiel #5
0
async def test_stream_keepalive(opp):
    """Test hls stream retries the stream when keepalive=True."""
    await async_setup_component(opp, "stream", {"stream": {}})

    # Setup demo HLS track
    source = "test_stream_keepalive_source"
    stream = create_stream(opp, source)
    track = stream.add_provider("hls")
    track.num_segments = 2

    cur_time = 0

    def time_side_effect():
        nonlocal cur_time
        if cur_time >= 80:
            stream.keepalive = False  # Thread should exit and be joinable.
        cur_time += 40
        return cur_time

    with patch("av.open") as av_open, patch(
            "openpeerpower.components.stream.time") as mock_time, patch(
                "openpeerpower.components.stream.STREAM_RESTART_INCREMENT", 0):
        av_open.side_effect = av.error.InvalidDataError(-2, "error")
        mock_time.time.side_effect = time_side_effect
        # Request stream
        stream.keepalive = True
        stream.start()
        stream._thread.join()
        stream._thread = None
        assert av_open.call_count == 2

    # Stop stream, if it hasn't quit already
    stream.stop()
Beispiel #6
0
async def test_recorder_log(opp, caplog):
    """Test starting a stream to record logs the url without username and password."""
    await async_setup_component(opp, "stream", {"stream": {}})
    stream = create_stream(opp, "https://*****:*****@foo.bar")
    with patch.object(opp.config, "is_allowed_path", return_value=True):
        await stream.async_record("/example/path")
    assert "https://*****:*****@foo.bar" not in caplog.text
    assert "https://****:****@foo.bar" in caplog.text
Beispiel #7
0
async def test_record_path_not_allowed(opp, opp_client):
    """Test where the output path is not allowed by open peer power configuration."""
    await async_setup_component(opp, "stream", {"stream": {}})

    # Setup demo track
    source = generate_h264_video()
    stream = create_stream(opp, source)
    with patch.object(opp.config, "is_allowed_path",
                      return_value=False), pytest.raises(OpenPeerPowerError):
        await stream.async_record("/example/path")
Beispiel #8
0
 async def create_stream(self) -> Stream | None:
     """Create a Stream for stream_source."""
     # There is at most one stream (a decode worker) per camera
     if not self.stream:
         async with async_timeout.timeout(CAMERA_STREAM_SOURCE_TIMEOUT):
             source = await self.stream_source()
         if not source:
             return None
         self.stream = create_stream(self.opp, source, options=self.stream_options)
     return self.stream
Beispiel #9
0
async def test_hls_playlist_view_no_output(opp, opp_client, hls_stream):
    """Test rendering the hls playlist with no output segments."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream = create_stream(opp, STREAM_SOURCE)
    stream.add_provider("hls")

    hls_client = await hls_stream(stream)

    # Fetch playlist
    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 404
Beispiel #10
0
async def test_record_stream_audio(opp, opp_client, stream_worker_sync,
                                   record_worker_sync):
    """
    Test treatment of different audio inputs.

    Record stream output should have an audio channel when input has
    a valid codec and audio packets and no audio channel otherwise.
    """
    await async_setup_component(opp, "stream", {"stream": {}})

    for a_codec, expected_audio_streams in (
        ("aac", 1),  # aac is a valid mp4 codec
        ("pcm_mulaw", 0),  # G.711 is not a valid mp4 codec
        ("empty", 0),  # audio stream with no packets
        (None, 0),  # no audio stream
    ):
        record_worker_sync.reset()
        stream_worker_sync.pause()

        # Setup demo track
        source = generate_h264_video(container_format="mov",
                                     audio_codec=a_codec)  # mov can store PCM
        stream = create_stream(opp, source)
        with patch.object(opp.config, "is_allowed_path", return_value=True):
            await stream.async_record("/example/path")
        recorder = stream.add_provider("recorder")

        while True:
            segment = await recorder.recv()
            if not segment:
                break
            last_segment = segment
            stream_worker_sync.resume()

        result = av.open(BytesIO(last_segment.init + last_segment.moof_data),
                         "r",
                         format="mp4")

        assert len(result.streams.audio) == expected_audio_streams
        result.close()
        stream.stop()
        await opp.async_block_till_done()

        # Verify that the save worker was invoked, then block until its
        # thread completes and is shutdown completely to avoid thread leaks.
        await record_worker_sync.join()
Beispiel #11
0
async def test_record_lookback(opp, opp_client, stream_worker_sync,
                               record_worker_sync):
    """Exercise record with loopback."""
    await async_setup_component(opp, "stream", {"stream": {}})

    source = generate_h264_video()
    stream = create_stream(opp, source)

    # Start an HLS feed to enable lookback
    stream.add_provider("hls")
    stream.start()

    with patch.object(opp.config, "is_allowed_path", return_value=True):
        await stream.async_record("/example/path", lookback=4)

    # This test does not need recorder cleanup since it is not fully exercised

    stream.stop()
Beispiel #12
0
async def test_hls_stream(opp, hls_stream, stream_worker_sync):
    """
    Test hls stream.

    Purposefully not mocking anything here to test full
    integration with the stream component.
    """
    await async_setup_component(opp, "stream", {"stream": {}})

    stream_worker_sync.pause()

    # Setup demo HLS track
    source = generate_h264_video()
    stream = create_stream(opp, source)

    # Request stream
    stream.add_provider("hls")
    stream.start()

    hls_client = await hls_stream(stream)

    # Fetch playlist
    playlist_response = await hls_client.get()
    assert playlist_response.status == 200

    # Fetch init
    playlist = await playlist_response.text()
    init_response = await hls_client.get("/init.mp4")
    assert init_response.status == 200

    # Fetch segment
    playlist = await playlist_response.text()
    segment_url = "/" + playlist.splitlines()[-1]
    segment_response = await hls_client.get(segment_url)
    assert segment_response.status == 200

    stream_worker_sync.resume()

    # Stop stream, if it hasn't quit already
    stream.stop()

    # Ensure playlist not accessible after stream ends
    fail_response = await hls_client.get()
    assert fail_response.status == HTTP_NOT_FOUND
Beispiel #13
0
async def test_stream_timeout(opp, opp_client, stream_worker_sync):
    """Test hls stream timeout."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream_worker_sync.pause()

    # Setup demo HLS track
    source = generate_h264_video()
    stream = create_stream(opp, source)

    # Request stream
    stream.add_provider("hls")
    stream.start()
    url = stream.endpoint_url("hls")

    http_client = await opp_client()

    # Fetch playlist
    parsed_url = urlparse(url)
    playlist_response = await http_client.get(parsed_url.path)
    assert playlist_response.status == 200

    # Wait a minute
    future = dt_util.utcnow() + timedelta(minutes=1)
    async_fire_time_changed(opp, future)

    # Fetch again to reset timer
    playlist_response = await http_client.get(parsed_url.path)
    assert playlist_response.status == 200

    stream_worker_sync.resume()

    # Wait 5 minutes
    future = dt_util.utcnow() + timedelta(minutes=5)
    async_fire_time_changed(opp, future)
    await opp.async_block_till_done()

    # Ensure playlist not accessible
    fail_response = await http_client.get(parsed_url.path)
    assert fail_response.status == HTTP_NOT_FOUND
Beispiel #14
0
async def test_hls_max_segments(opp, hls_stream, stream_worker_sync):
    """Test rendering the hls playlist with more segments than the segment deque can hold."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream = create_stream(opp, STREAM_SOURCE)
    stream_worker_sync.pause()
    hls = stream.add_provider("hls")

    hls_client = await hls_stream(stream)

    # Produce enough segments to overfill the output buffer by one
    for sequence in range(1, MAX_SEGMENTS + 2):
        hls.put(Segment(sequence, INIT_BYTES, MOOF_BYTES, DURATION))
        await opp.async_block_till_done()

    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 200

    # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist.
    start = MAX_SEGMENTS + 2 - NUM_PLAYLIST_SEGMENTS
    segments = []
    for sequence in range(start, MAX_SEGMENTS + 2):
        segments.append(make_segment(sequence))
    assert await resp.text() == make_playlist(
        sequence=start,
        segments=segments,
    )

    # The segment that fell off the buffer is not accessible
    segment_response = await hls_client.get("/segment/1.m4s")
    assert segment_response.status == 404

    # However all segments in the buffer are accessible, even those that were not in the playlist.
    for sequence in range(2, MAX_SEGMENTS + 2):
        segment_response = await hls_client.get(f"/segment/{sequence}.m4s")
        assert segment_response.status == 200

    stream_worker_sync.resume()
    stream.stop()
Beispiel #15
0
async def test_stream_timeout_after_stop(opp, opp_client, stream_worker_sync):
    """Test hls stream timeout after the stream has been stopped already."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream_worker_sync.pause()

    # Setup demo HLS track
    source = generate_h264_video()
    stream = create_stream(opp, source)

    # Request stream
    stream.add_provider("hls")
    stream.start()

    stream_worker_sync.resume()
    stream.stop()

    # Wait 5 minutes and fire callback.  Stream should already have been
    # stopped so this is a no-op.
    future = dt_util.utcnow() + timedelta(minutes=5)
    async_fire_time_changed(opp, future)
    await opp.async_block_till_done()
Beispiel #16
0
async def test_hls_max_segments_discontinuity(opp, hls_stream,
                                              stream_worker_sync):
    """Test a discontinuity with more segments than the segment deque can hold."""
    await async_setup_component(opp, "stream", {"stream": {}})

    stream = create_stream(opp, STREAM_SOURCE)
    stream_worker_sync.pause()
    hls = stream.add_provider("hls")

    hls_client = await hls_stream(stream)

    hls.put(Segment(1, INIT_BYTES, MOOF_BYTES, DURATION, stream_id=0))

    # Produce enough segments to overfill the output buffer by one
    for sequence in range(1, MAX_SEGMENTS + 2):
        hls.put(
            Segment(sequence, INIT_BYTES, MOOF_BYTES, DURATION, stream_id=1))
    await opp.async_block_till_done()

    resp = await hls_client.get("/playlist.m3u8")
    assert resp.status == 200

    # 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 + 2 - NUM_PLAYLIST_SEGMENTS
    segments = []
    for sequence in range(start, MAX_SEGMENTS + 2):
        segments.append(make_segment(sequence))
    assert await resp.text() == make_playlist(
        sequence=start,
        discontinuity_sequence=1,
        segments=segments,
    )

    stream_worker_sync.resume()
    stream.stop()