def test_playing_field_equality(prop, value1, value2): playing1 = Playing(**{prop: value1}) playing2 = Playing(**{prop: value2}) playing3 = Playing(**{prop: value2}) assert playing1 == playing1 assert playing1 != playing2 assert playing2 == playing3
async def _perform_update(expected_updates, expected_protocol): dmap_pusher.post_update( Playing(MediaType.Music, DeviceState.Idle, title=f"dmap_{expected_updates}")) mrp_pusher.post_update( Playing(MediaType.Music, DeviceState.Idle, title=f"mrp_{expected_updates}")) await until(lambda: listener.no_of_updates == expected_updates) assert listener.last_update.title == f"{expected_protocol}_{expected_updates}"
def test_playing_generate_same_hash(): playing = Playing(title="title", artist="artist", album="album", total_time=123) assert ( "538df531d1715629fdd87affd0c5957bcbf54cd89180778071e6535b7df4e22c" == playing.hash ) playing2 = Playing(title="dummy", artist="test", album="none", total_time=321) assert ( "80045c05d18382f33a5369fd5cdfc6ae42c3eb418125f638d7a31ab173b01ade" == playing2.hash )
async def playing(self) -> Playing: """Return what is currently playing.""" if self._state_manager.metadata is None: return Playing(device_state=const.DeviceState.Idle, media_type=const.MediaType.Unknown) metadata = self._state_manager.metadata return Playing( device_state=const.DeviceState.Playing, media_type=const.MediaType.Music, title=metadata.title, artist=metadata.artist, album=metadata.album, )
async def playing(self) -> Playing: """Return what is currently playing.""" if self._playback_manager.playback_info is None: return Playing(device_state=const.DeviceState.Idle, media_type=const.MediaType.Unknown) metadata = self._playback_manager.playback_info.metadata total_time = int(metadata.duration) if metadata.duration else None return Playing( device_state=const.DeviceState.Playing, media_type=const.MediaType.Music, title=metadata.title, artist=metadata.artist, album=metadata.album, position=int(self._playback_manager.playback_info.position), total_time=total_time, )
def test_playing_title_artist_album_genre(): out = str( Playing(title="mytitle", artist="myartist", album="myalbum", genre="mygenre") ) assert "mytitle" in out assert "myartist" in out assert "myalbum" in out assert "mygenre" in out
async def dispatch_device_state(mrp_state_dispatcher, state, protocol=Protocol.MRP): event = asyncio.Event() # Add a listener last in the last and make it set an asyncio.Event. That way we # can synchronize and know that all other listeners have been called. mrp_state_dispatcher.listen_to(UpdatedState.Playing, lambda message: event.set()) mrp_state_dispatcher.dispatch(UpdatedState.Playing, Playing(MediaType.Unknown, state)) await event.wait()
def test_post_ignore_duplicate_update(event_loop, updates): listener = MagicMock() playing = Playing() async def _post_updates(repeats: int): updater = PushUpdaterDummy(event_loop) updater.listener = listener for _ in range(repeats): updater.post_update(playing) event_loop.run_until_complete(_post_updates(updates)) assert listener.playstatus_update.call_count == 1 listener.playstatus_update.assert_called_once_with(ANY, playing)
def test_playing_basic_fields(): out = str( Playing( title="mytitle", artist="myartist", album="myalbum", genre="mygenre", series_name="myseries", season_number=1245, episode_number=2468, )) assert "mytitle" in out assert "myartist" in out assert "myalbum" in out assert "mygenre" in out assert "myseries" in out assert "1245" in out assert "2468" in out
def test_post_ignore_duplicate_update(event_loop, state_dispatcher, updates): listener = MagicMock() playing = Playing() def _state_changed(message): assert message.protocol == Protocol.MRP assert message.state == UpdatedState.Playing listener.state_updated(message.value) async def _post_updates(repeats: int): updater = PushUpdaterDummy(state_dispatcher) updater.listener = listener state_dispatcher.listen_to(UpdatedState.Playing, _state_changed) for _ in range(repeats): updater.post_update(playing) event_loop.run_until_complete(_post_updates(updates)) assert listener.playstatus_update.call_count == 1 listener.playstatus_update.assert_called_once_with(ANY, playing) listener.state_updated.assert_called_once_with(playing)
def build_playing_instance(state: PlayerState) -> Playing: """Build a Playing instance from play state.""" def media_type() -> MediaType: """Type of media is currently playing, e.g. video, music.""" if state.metadata: media_type = state.metadata.mediaType if media_type == cim.Audio: return MediaType.Music if media_type == cim.Video: return MediaType.Video return MediaType.Unknown def device_state() -> DeviceState: """Device state, e.g. playing or paused.""" return { None: DeviceState.Idle, PlaybackState.Playing: DeviceState.Playing, PlaybackState.Paused: DeviceState.Paused, PlaybackState.Stopped: DeviceState.Stopped, PlaybackState.Interrupted: DeviceState.Loading, PlaybackState.Seeking: DeviceState.Seeking, }.get(state.playback_state, DeviceState.Paused) def title() -> Optional[str]: """Title of the current media, e.g. movie or song name.""" return state.metadata_field("title") def artist() -> Optional[str]: """Artist of the currently playing song.""" return state.metadata_field("trackArtistName") def album() -> Optional[str]: """Album of the currently playing song.""" return state.metadata_field("albumName") def genre() -> Optional[str]: """Genre of the currently playing song.""" return state.metadata_field("genre") def total_time() -> Optional[int]: """Total play time in seconds.""" duration = state.metadata_field("duration") if duration is None or math.isnan(duration): return None return int(duration) def position() -> Optional[int]: """Position in the playing media (seconds).""" elapsed_timestamp = state.metadata_field("elapsedTimeTimestamp") # If we don't have reference time, we can't do anything if not elapsed_timestamp: return None elapsed_time: int = state.metadata_field("elapsedTime") or 0 diff = (datetime.datetime.now() - _cocoa_to_timestamp(elapsed_timestamp)).total_seconds() if device_state() == DeviceState.Playing: return int(elapsed_time + diff) return int(elapsed_time) def shuffle() -> ShuffleState: """If shuffle is enabled or not.""" info = state.command_info(CommandInfo_pb2.ChangeShuffleMode) if info is None: return ShuffleState.Off if info.shuffleMode == protobuf.ShuffleMode.Off: return ShuffleState.Off if info.shuffleMode == protobuf.ShuffleMode.Albums: return ShuffleState.Albums return ShuffleState.Songs def repeat() -> RepeatState: """Repeat mode.""" info = state.command_info(CommandInfo_pb2.ChangeRepeatMode) if info is None: return RepeatState.Off if info.repeatMode == protobuf.RepeatMode.One: return RepeatState.Track if info.repeatMode == protobuf.RepeatMode.All: return RepeatState.All return RepeatState.Off def hash() -> str: """Create a unique hash for what is currently playing.""" return state.item_identifier return Playing( media_type=media_type(), device_state=device_state(), title=title(), artist=artist(), album=album(), genre=genre(), total_time=total_time(), position=position(), shuffle=shuffle(), repeat=repeat(), hash=hash(), )
def test_playing_only_total_time(): assert "5678" in str(Playing(total_time=5678))
def build_playing_instance(playstatus) -> Playing: """Build a Playing instance from play state.""" def _get_time_in_seconds(tag) -> int: time = parser.first(playstatus, "cmst", tag) return daap.ms_to_s(time) def media_type() -> MediaType: """Type of media is currently playing, e.g. video, music.""" state = parser.first(playstatus, "cmst", "caps") if not state: return MediaType.Unknown mediakind = parser.first(playstatus, "cmst", "cmmk") if mediakind is not None: return daap.media_kind(mediakind) # Fallback: if artist or album exists we assume music (not present # for video) if artist() or album(): return MediaType.Music return MediaType.Video def device_state() -> DeviceState: """Device state, e.g. playing or paused.""" state = parser.first(playstatus, "cmst", "caps") return daap.playstate(state) def title() -> Optional[str]: """Title of the current media, e.g. movie or song name.""" return parser.first(playstatus, "cmst", "cann") def artist() -> Optional[str]: """Arist of the currently playing song.""" return parser.first(playstatus, "cmst", "cana") def album() -> Optional[str]: """Album of the currently playing song.""" return parser.first(playstatus, "cmst", "canl") def genre() -> Optional[str]: """Genre of the currently playing song.""" return parser.first(playstatus, "cmst", "cang") def total_time() -> Optional[int]: """Total play time in seconds.""" return _get_time_in_seconds("cast") def position() -> Optional[int]: """Position in the playing media (seconds).""" total = total_time() remaining_time = _get_time_in_seconds("cant") if not total or not remaining_time: return None return total - remaining_time def shuffle() -> Optional[ShuffleState]: """If shuffle is enabled or not.""" state = parser.first(playstatus, "cmst", "cash") if state is None or state == 0: return ShuffleState.Off # DMAP does not support the "albums" state and will always report # "songs" if shuffle is active return ShuffleState.Songs def repeat() -> Optional[RepeatState]: """Repeat mode.""" state = parser.first(playstatus, "cmst", "carp") if state is None: return RepeatState.Off return RepeatState(state) return Playing( media_type=media_type(), device_state=device_state(), title=title(), artist=artist(), album=album(), genre=genre(), total_time=total_time(), position=position(), shuffle=shuffle(), repeat=repeat(), )
def test_playing_position_force_in_range(position, total_time, expected): assert Playing(position=position, total_time=total_time).position == expected
def test_playing_media_type_and_playstate(): out = str( Playing(media_type=MediaType.Video, device_state=DeviceState.Playing)) assert convert.media_type_str(MediaType.Video) in out assert convert.device_state_str(DeviceState.Playing) in out
def test_playing_init_field_values(prop, value1, value2): playing = Playing(**{prop: value1}) assert getattr(playing, prop) == value1
def test_playing_eq_ensure_member_count(): # Fail if a property is added or removed to interface, just as a reminder to # update equality comparison assert len(Playing().__dict__) == 14
def test_playing_custom_hash(): playing = Playing(hash="dummy") assert playing.hash == "dummy"
def test_playing_shuffle_and_repeat(): out = str(Playing(shuffle=ShuffleState.Songs, repeat=RepeatState.Track)) assert "Shuffle: Songs" in out assert "Repeat: Track" in out
def test_playing_both_position_and_total_time(): out = str(Playing(position=1234, total_time=5678)) assert "1234/5678" in out
def test_playing_only_position(): assert "1234" in str(Playing(position=1234))