Esempio n. 1
0
async def test_only_allow_one_stream_at_the_time(raop_client):
    # This is not pretty, but the idea is to start two concurrent streaming tasks, wait
    # for them to finish and verify that one of them raised an exception. This is to
    # avoid making any assumptions regarding in which order they are scheduled on the
    # event loop.
    result = await asyncio.gather(
        raop_client.stream.stream_file(data_path("audio_3_packets.wav")),
        raop_client.stream.stream_file(data_path("only_metadata.wav")),
        return_exceptions=True,
    )

    result.remove(None)  # Should be one None for success and one exception
    assert len(result) == 1
    assert isinstance(result[0], exceptions.InvalidStateError)
Esempio n. 2
0
async def test_remote_control_features(raop_client, playing_listener):
    assert_features_in_state(
        raop_client.features.all_features(),
        REMOTE_CONTROL_FIELDS,
        FeatureState.Unavailable,
    )

    # Start playback in the background
    future = asyncio.ensure_future(
        raop_client.stream.stream_file(data_path("audio_3_packets.wav")))

    # Wait for device to move to playing state and verify feature state
    await playing_listener.playing_event.wait()
    assert_features_in_state(
        raop_client.features.all_features(),
        REMOTE_CONTROL_FIELDS,
        FeatureState.Available,
    )

    await future

    # Playback finished so no controls should be available
    assert_features_in_state(
        raop_client.features.all_features(),
        REMOTE_CONTROL_FIELDS,
        FeatureState.Unavailable,
    )
Esempio n. 3
0
async def test_stream_with_password(
    raop_state,
    raop_usecase,
    raop_conf,
    raop_server_password,
    raop_client_password,
    event_loop,
):
    raop_usecase.password(raop_server_password)

    raop_service = raop_conf.get_service(Protocol.RAOP)
    if raop_service:
        raop_service.password = raop_client_password

    expect_error = raop_server_password != raop_client_password

    client = await connect(raop_conf, loop=event_loop)
    try:
        await client.stream.stream_file(data_path("audio_10_frames.wav"))
        assert not expect_error
    except AuthenticationError as e:
        assert expect_error
    except Exception as e:
        assert False
    finally:
        await asyncio.gather(*client.close())
Esempio n. 4
0
async def test_stream_complete_file_verify_padding(raop_client, raop_state):
    await raop_client.stream.stream_file(data_path("audio_10_frames.wav"))

    # The last packet is padded with zeroes to fill a complete packet. This means that
    # (FRAME_PER_PACKET - 10) = 342 empty frames are added to the first packet. After
    # that, empty packets are sent corresponding to the length of the latency, i.e.
    # (sample_rate + 22050) / FRAMES_PER_PACKET which gives 187.9 packets with
    # sample_rate=44100, channels=2, sample_width=2. Latency is a bit unfortunate
    # chosen to not divide evenly, but should be rounded up to 188.

    # Based on content in input file
    sample_rate = 44100
    frame_size = CHANNELS * SAMPLE_WIDTH

    # Skip initial audio frames and just extract padding
    padding = raop_state.raw_audio[frame_size * 10:]

    # Number of frames used for padding in last audio packet (i.e. first packet here)
    padding_frames_in_audio_packet = FRAMES_PER_PACKET - 10

    # Number of frames for pure padding
    hardcoded_additional_latency = 22050
    latency_packets = math.ceil(
        (sample_rate + hardcoded_additional_latency) / FRAMES_PER_PACKET)
    latency_frames = latency_packets * FRAMES_PER_PACKET

    # Calculate total number of latency frames and convert that to number of bytes
    total_latency_frames = padding_frames_in_audio_packet + latency_frames
    total_latency_size_in_bytes = total_latency_frames * frame_size

    assert len(padding) == total_latency_size_in_bytes

    # Avoid allocating a large buffer to compare with
    assert padding.replace(b"\x00", b"") == b""
Esempio n. 5
0
async def test_muted_volume_from_receiver(raop_client, raop_state,
                                          raop_usecase):
    raop_usecase.initial_audio_level_supported(True)
    raop_state.volume = -144.0

    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    assert math.isclose(raop_client.audio.volume, 0.0)
Esempio n. 6
0
async def test_stream_from_buffer(raop_client, raop_state):
    with io.open(data_path("audio_1_packet_metadata.wav"),
                 "rb") as source_file:
        await raop_client.stream.stream_file(source_file)

    assert raop_state.metadata.artist == "postlund"
    assert raop_state.metadata.album == "raop"
    assert raop_state.metadata.title == "pyatv"
Esempio n. 7
0
async def test_stream_complete_legacy_auth(raop_client, raop_state,
                                           raop_usecase, require_auth):
    raop_usecase.require_auth(require_auth)

    await raop_client.stream.stream_file(data_path("audio_10_frames.wav"))

    assert raop_state.auth_setup_performed == require_auth
    assert await audio_matches(raop_state.raw_audio, frames=10)
Esempio n. 8
0
async def test_set_volume_prior_to_streaming(raop_client, raop_state):
    # Initial client sound level
    assert math.isclose(raop_client.audio.volume, 33.0)

    await raop_client.audio.set_volume(60)
    assert math.isclose(raop_client.audio.volume, 60)

    await raop_client.stream.stream_file(data_path("only_metadata.wav"))
    assert math.isclose(raop_state.volume, -12.0)
Esempio n. 9
0
async def test_send_feedback(raop_client, raop_usecase, raop_state,
                             feedback_supported):
    raop_usecase.feedback_enabled(feedback_supported)

    await raop_client.stream.stream_file(data_path("audio_3_packets.wav"))

    # One request is sent to see if feedback is supported, then additional requests are
    # only sent if actually supported
    if feedback_supported:
        assert raop_state.feedback_packets_received > 1
    else:
        assert raop_state.feedback_packets_received == 1
Esempio n. 10
0
    async def test_play_local_file(self):
        self.airplay_usecase.airplay_playback_idle()
        self.airplay_usecase.airplay_playback_playing()
        self.airplay_usecase.airplay_playback_idle()

        await self.atv.stream.play_url(data_path("testfile.txt"))

        self.assertRegex(self.airplay_state.last_airplay_url,
                         r"http://127.0.0.1:[0-9]+/testfile.txt")
        self.assertEqual(self.airplay_state.last_airplay_start, 0)
        self.assertIsNotNone(self.airplay_state.last_airplay_uuid)
        self.assertEqual(self.airplay_state.last_airplay_content,
                         b"a file for testing")
Esempio n. 11
0
async def test_stop_playback(raop_client, raop_state, button):
    async def _fake_sleep(time: float = None, loop=None):
        async def dummy():
            pass

        await getattr(raop_client.remote_control, button)()
        await asyncio.ensure_future(dummy())

    # The idea here is to simulate calling "stop" after the first frame has been sent,
    # i.e. after the first "sleep" has been made. It's a bit tied to implementation
    # details but good enough.
    stub_sleep(fn=_fake_sleep)

    await raop_client.stream.stream_file(data_path("audio_3_packets.wav"))

    assert len(raop_state.raw_audio) == ONE_FRAME_IN_BYTES
Esempio n. 12
0
async def test_stream_retransmission(raop_client, raop_state, raop_usecase,
                                     drop_packets, enable_retransmission):
    raop_usecase.retransmissions_enabled(enable_retransmission)
    raop_usecase.drop_n_packets(drop_packets)

    await raop_client.stream.stream_file(data_path("audio_3_packets.wav"))

    # If retransmissions are enabled, then we should always receive all packets in
    # the end (within reasons). If retransmissions are not enabled, then we should
    # start comparing the received audio stream after the amount of audio packets
    # dropped.
    start_frame = 0 if enable_retransmission else drop_packets * FRAMES_PER_PACKET
    assert audio_matches(
        raop_state.raw_audio,
        frames=3 * FRAMES_PER_PACKET - start_frame,  # Total expected frame
        skip_frames=start_frame,  # Skipping first amount of frames
    )
Esempio n. 13
0
async def test_set_volume_during_playback(raop_client, raop_state,
                                          playing_listener):
    # Set maximum volume as initial volume
    await raop_client.audio.set_volume(100.0)

    # Start playback in the background
    future = asyncio.ensure_future(
        raop_client.stream.stream_file(data_path("audio_3_packets.wav")))

    # Wait for device to move to playing state and verify volume
    await playing_listener.playing_event.wait()
    assert math.isclose(raop_state.volume, -0.0)

    # Change volume, which we now know will happen during playback
    await raop_client.audio.set_volume(50.0)
    assert math.isclose(raop_state.volume, -15.0)

    await future
Esempio n. 14
0
async def test_push_updates(raop_client, playing_listener):
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # Initial idle + audio playing + back to idle
    await until(lambda: len(playing_listener.updates) == 3)

    idle = playing_listener.updates[0]
    assert idle.device_state == DeviceState.Idle
    assert idle.media_type == MediaType.Unknown

    playing = playing_listener.updates[1]
    assert playing.device_state == DeviceState.Playing
    assert playing.media_type == MediaType.Music
    assert playing.artist == "postlund"
    assert playing.title == "pyatv"
    assert playing.album == "raop"

    idle = playing_listener.updates[2]
    assert idle.device_state == DeviceState.Idle
    assert idle.media_type == MediaType.Unknown
Esempio n. 15
0
async def test_use_default_volume_from_device(
    raop_client,
    raop_state,
    raop_usecase,
    initial_level_supported,
    sender_expected,
    receiver_expected,
):
    raop_usecase.initial_audio_level_supported(initial_level_supported)

    # Prior to streaming, we don't know the volume of the receiver so return default level
    assert math.isclose(raop_client.audio.volume, 33.0)

    # Default level on remote device
    assert math.isclose(raop_state.volume, -15.0)

    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # Level on the client and receiver should match now
    assert math.isclose(raop_state.volume, receiver_expected)
    assert math.isclose(raop_client.audio.volume, sender_expected)
Esempio n. 16
0
async def test_metadata_features(raop_client):
    class FeatureListener(PushListener):
        def __init__(self):
            """Initialize a new FeatureListener instance."""
            self.all_features: Dict[FeatureName, FeatureInfo] = {}

        def playstatus_update(self, updater, playstatus: Playing) -> None:
            """Inform about changes to what is currently playing."""
            if playstatus.device_state == DeviceState.Playing:
                self.all_features = raop_client.features.all_features()

        def playstatus_error(self, updater, exception: Exception) -> None:
            """Inform about an error when updating play status."""

    listener = FeatureListener()
    raop_client.push_updater.listener = listener
    raop_client.push_updater.start()

    # All features should be unavailable when nothing is playing
    assert_features_in_state(
        raop_client.features.all_features(),
        METADATA_FIELDS,
        FeatureState.Unavailable,
    )

    # StreamFile should be available for streaming
    assert (raop_client.features.get_feature(
        FeatureName.StreamFile).state == FeatureState.Available)
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # Use a listener to catch when something starts playing and save that as it's
    # too late to verify when stream_file returns (idle state will be reported).
    await until(lambda: listener.all_features)

    # When playing, everything should be available
    assert_features_in_state(
        listener.all_features,
        METADATA_FIELDS,
        FeatureState.Available,
    )
Esempio n. 17
0
async def test_stream_retransmission(raop_client, raop_state, raop_usecase,
                                     drop_packets, enable_retransmission):
    raop_usecase.retransmissions_enabled(enable_retransmission)
    raop_usecase.drop_n_packets(drop_packets)

    await raop_client.stream.stream_file(data_path("audio_3_packets.wav"))

    # For stability reasons: wait for all packets to be received as it might take a few
    # extra runs for the event loop to catch up
    packets_to_receive = 3 if enable_retransmission else 1
    await until(lambda: len(raop_state.audio_packets) >= packets_to_receive)

    # If retransmissions are enabled, then we should always receive all packets in
    # the end (within reasons). If retransmissions are not enabled, then we should
    # start comparing the received audio stream after the amount of audio packets
    # dropped.
    start_frame = 0 if enable_retransmission else drop_packets * FRAMES_PER_PACKET
    assert await audio_matches(
        raop_state.raw_audio,
        frames=3 * FRAMES_PER_PACKET - start_frame,  # Total expected frame
        skip_frames=start_frame,  # Skipping first amount of frames
    )
Esempio n. 18
0
async def test_push_updates_progress(raop_client, playing_listener):
    assert_features_in_state(
        raop_client.features.all_features(),
        PROGRESS_FIELDS,
        FeatureState.Unavailable,
    )

    await raop_client.stream.stream_file(data_path("static_3sec.ogg"))

    # Initial idle + audio playing + back to idle
    await until(lambda: len(playing_listener.updates) == 3)

    playing = playing_listener.updates[1]
    assert playing.device_state == DeviceState.Playing
    assert playing.position == 0
    assert playing.total_time == 3

    assert_features_in_state(
        playing_listener.all_features,
        PROGRESS_FIELDS,
        FeatureState.Available,
    )
Esempio n. 19
0
async def test_metadata_features(raop_client, playing_listener):
    # All features should be unavailable when nothing is playing
    assert_features_in_state(
        raop_client.features.all_features(),
        METADATA_FIELDS,
        FeatureState.Unavailable,
    )

    # StreamFile should be available for streaming
    assert (raop_client.features.get_feature(
        FeatureName.StreamFile).state == FeatureState.Available)
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # Use a listener to catch when something starts playing and save that as it's
    # too late to verify when stream_file returns (idle state will be reported).
    await until(lambda: playing_listener.all_features)

    # When playing, everything should be available
    assert_features_in_state(
        playing_listener.all_features,
        METADATA_FIELDS,
        FeatureState.Available,
    )
Esempio n. 20
0
async def test_push_updates(raop_client):
    class TestListener(PushListener):
        def __init__(self):
            """Initialize a new TestListener instance."""
            self.updates: List[Playing] = []

        def playstatus_update(self, updater, playstatus: Playing) -> None:
            """Inform about changes to what is currently playing."""
            self.updates.append(playstatus)

        def playstatus_error(self, updater, exception: Exception) -> None:
            """Inform about an error when updating play status."""

    listener = TestListener()
    raop_client.push_updater.listener = listener
    raop_client.push_updater.start()

    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # Initial idle + audio playing + back to idle
    await until(lambda: len(listener.updates) == 3)

    idle = listener.updates[0]
    assert idle.device_state == DeviceState.Idle
    assert idle.media_type == MediaType.Unknown

    playing = listener.updates[1]
    assert playing.device_state == DeviceState.Playing
    assert playing.media_type == MediaType.Music
    assert playing.artist == "postlund"
    assert playing.title == "pyatv"
    assert playing.album == "raop"

    idle = listener.updates[2]
    assert idle.device_state == DeviceState.Idle
    assert idle.media_type == MediaType.Unknown
Esempio n. 21
0
async def test_stream_file_verify_metadata(raop_client, raop_state, metadata):
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))
    assert raop_state.metadata.artist == metadata["artist"]
    assert raop_state.metadata.album == metadata["album"]
    assert raop_state.metadata.title == metadata["title"]
Esempio n. 22
0
async def test_stream_complete_legacy_auth(raop_client, raop_state):
    await raop_client.stream.stream_file(data_path("audio_10_frames.wav"))

    assert raop_state.auth_setup_performed
    assert audio_matches(raop_state.raw_audio, frames=10)
Esempio n. 23
0
async def test_is_streamable_supported_file(test_file, streamable):
    assert await helpers.is_streamable(data_path(test_file)) == streamable
Esempio n. 24
0
async def test_teardown_called_after_playback(raop_client, raop_state):
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))
    assert raop_state.teardown_called
Esempio n. 25
0
async def test_sync_packets(raop_client, raop_state):
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    # TODO: This test doesn't really test anything, just makes sure that sync packets
    # are received. Expand this test in the future.
    await until(lambda: raop_state.sync_packets_received > 5)
Esempio n. 26
0
async def test_device_not_supporting_info_requests(raop_client, raop_usecase):
    raop_usecase.supports_info(False)

    # Should just not crash with an error if endpoint is not supported
    await raop_client.stream.stream_file(data_path("only_metadata.wav"))
Esempio n. 27
0
async def test_stream_complete_file(raop_client, raop_state):
    await raop_client.stream.stream_file(data_path("audio_10_frames.wav"))

    assert audio_matches(raop_state.raw_audio, frames=10)
Esempio n. 28
0
async def test_mute_volume_from_client(raop_client, raop_state):
    await raop_client.audio.set_volume(0.0)

    await raop_client.stream.stream_file(data_path("only_metadata.wav"))

    assert math.isclose(raop_state.volume, -144.0)