Skip to content

Commit

Permalink
Rename play_state to device_state
Browse files Browse the repository at this point in the history
This commit changes play state to device state and consolidates states
a bit to make them fit better between MRP and DMAP. In the long run,
a standby state shall be added as well once that is supported.
  • Loading branch information
postlund committed Dec 18, 2019
1 parent 1c5f3b3 commit e3ac495
Show file tree
Hide file tree
Showing 15 changed files with 119 additions and 242 deletions.
16 changes: 8 additions & 8 deletions docs/documentation/atvremote.md
Expand Up @@ -103,8 +103,8 @@ without having to ask the device for it:

$ atvremote -n Kitchen push_updates
Press ENTER to stop
Media type: Unknown
Play state: Paused
Media type: Unknown
Device state: Paused
--------------------

Updates will be displayed when they happen. Just press ENTER to stop.
Expand Down Expand Up @@ -148,7 +148,7 @@ called `commands`, as it will present a list of availble commands:
- genre - Genre of the currently playing song
- hash - Create a unique hash for what is currently playing
- media_type - Type of media is currently playing, e.g. video, music
- play_state - Play state, e.g. playing or paused
- device_state - Device state, e.g. playing or paused
- position - Position in the playing media (seconds)
- repeat - Repeat mode
- shuffle - If shuffle is enabled or not
Expand All @@ -172,11 +172,11 @@ called `commands`, as it will present a list of availble commands:
You can for instance get what is currently playing with `playing`:

$ atvremote --id 40:CB:C0:A8:DE:9A playing
Media type: Music
Play state: Playing
Position: 0/397s (0.0%)
Repeat: Off
Shuffle: False
Media type: Music
Device state: Playing
Position: 0/397s (0.0%)
Repeat: Off
Shuffle: False

Or seek in the currently playing media:

Expand Down
23 changes: 8 additions & 15 deletions pyatv/const.py
Expand Up @@ -34,32 +34,25 @@
MEDIA_TYPE_TV = 4


# Play states
# Device states

#: Device is in idle state
PLAY_STATE_IDLE = 0

#: No media is currently select/playing
PLAY_STATE_NO_MEDIA = 1
DEVICE_STATE_IDLE = 0

#: Media is loading/buffering
PLAY_STATE_LOADING = 2
DEVICE_STATE_LOADING = 1

#: Media is paused
PLAY_STATE_PAUSED = 3
DEVICE_STATE_PAUSED = 2

#: Media is playing
PLAY_STATE_PLAYING = 4

#: Media is being fast forwarded
PLAY_STATE_FAST_FORWARD = 5

#: Media is being rewinded
PLAY_STATE_FAST_BACKWARD = 6
DEVICE_STATE_PLAYING = 3

#: Media is stopped
PLAY_STATE_STOPPED = 7
DEVICE_STATE_STOPPED = 4

#: Media is seeking
DEVICE_STATE_SEEKING = 5

# Repeat states

Expand Down
41 changes: 17 additions & 24 deletions pyatv/convert.py
Expand Up @@ -33,45 +33,38 @@ def media_type_str(mediatype):
def playstate(state):
"""Convert iTunes playstate to API representation."""
# pylint: disable=too-many-return-statements
if state is None:
return const.PLAY_STATE_NO_MEDIA
if state == 0:
return const.PLAY_STATE_IDLE
if state == 0 or state is None:
return const.DEVICE_STATE_IDLE
if state == 1:
return const.PLAY_STATE_LOADING
return const.DEVICE_STATE_LOADING
if state == 2:
return const.PLAY_STATE_STOPPED
return const.DEVICE_STATE_STOPPED
if state == 3:
return const.PLAY_STATE_PAUSED
return const.DEVICE_STATE_PAUSED
if state == 4:
return const.PLAY_STATE_PLAYING
if state == 5:
return const.PLAY_STATE_FAST_FORWARD
if state == 6:
return const.PLAY_STATE_FAST_BACKWARD
return const.DEVICE_STATE_PLAYING
if state in (5, 6):
return const.DEVICE_STATE_SEEKING

raise exceptions.UnknownPlayState('Unknown playstate: ' + str(state))


# pylint: disable=too-many-return-statements
def playstate_str(state):
"""Convert internal API playstate to string."""
if state == const.PLAY_STATE_NO_MEDIA:
return 'No media'
if state == const.PLAY_STATE_IDLE:
if state == const.DEVICE_STATE_IDLE:
return 'Idle'
if state == const.PLAY_STATE_LOADING:
if state == const.DEVICE_STATE_LOADING:
return 'Loading'
if state == const.PLAY_STATE_PAUSED:
if state == const.DEVICE_STATE_STOPPED:
return 'Stopped'
if state == const.DEVICE_STATE_PAUSED:
return 'Paused'
if state == const.PLAY_STATE_PLAYING:
if state == const.DEVICE_STATE_PLAYING:
return 'Playing'
if state == const.PLAY_STATE_FAST_FORWARD:
return 'Fast forward'
if state == const.PLAY_STATE_FAST_BACKWARD:
return 'Fast backward'
if state == const.PLAY_STATE_STOPPED:
return 'Stopped'
if state == const.DEVICE_STATE_SEEKING:
return 'Seeking'

return 'Unsupported'


Expand Down
4 changes: 2 additions & 2 deletions pyatv/dmap/__init__.py
Expand Up @@ -236,8 +236,8 @@ def media_type(self):
return const.MEDIA_TYPE_VIDEO

@property
def play_state(self):
"""Play state, e.g. playing or paused."""
def device_state(self):
"""Device state, e.g. playing or paused."""
state = parser.first(self.playstatus, 'cmst', 'caps')
return convert.playstate(state)

Expand Down
28 changes: 14 additions & 14 deletions pyatv/interface.py
Expand Up @@ -211,39 +211,39 @@ class Playing:
def __str__(self):
"""Convert this playing object to a readable string."""
output = []
output.append('Media type: {0}'.format(
output.append(' Media type: {0}'.format(
convert.media_type_str(self.media_type)))
output.append('Play state: {0}'.format(
convert.playstate_str(self.play_state)))
output.append('Device state: {0}'.format(
convert.playstate_str(self.device_state)))

if self.title is not None:
output.append(' Title: {0}'.format(self.title))
output.append(' Title: {0}'.format(self.title))

if self.artist is not None:
output.append(' Artist: {0}'.format(self.artist))
output.append(' Artist: {0}'.format(self.artist))

if self.album is not None:
output.append(' Album: {0}'.format(self.album))
output.append(' Album: {0}'.format(self.album))

if self.genre is not None:
output.append(' Genre: {0}'.format(self.genre))
output.append(' Genre: {0}'.format(self.genre))

position = self.position
total_time = self.total_time
if position is not None and total_time is not None and total_time != 0:
output.append(' Position: {0}/{1}s ({2:.1%})'.format(
output.append(' Position: {0}/{1}s ({2:.1%})'.format(
position, total_time, float(position)/float(total_time)))
elif position is not None and position != 0:
output.append(' Position: {0}s'.format(position))
output.append(' Position: {0}s'.format(position))
elif total_time is not None and position != 0:
output.append('Total time: {0}s'.format(total_time))
output.append(' Total time: {0}s'.format(total_time))

if self.repeat is not None:
output.append(' Repeat: {0}'.format(
output.append(' Repeat: {0}'.format(
convert.repeat_str(self.repeat)))

if self.shuffle is not None:
output.append(' Shuffle: {0}'.format(self.shuffle))
output.append(' Shuffle: {0}'.format(self.shuffle))

return '\n'.join(output)

Expand All @@ -264,8 +264,8 @@ def media_type(self):
raise exceptions.NotSupportedError

@abstractproperty
def play_state(self):
"""Play state, e.g. playing or paused."""
def device_state(self):
"""Device state, e.g. playing or paused."""
raise exceptions.NotSupportedError

@abstractproperty
Expand Down
23 changes: 12 additions & 11 deletions pyatv/mrp/__init__.py
@@ -1,3 +1,4 @@

"""Implementation of the MediaRemoteTV Protocol used by ATV4 and later."""

import logging
Expand Down Expand Up @@ -177,24 +178,24 @@ def media_type(self):
return const.MEDIA_TYPE_UNKNOWN

@property
def play_state(self):
"""Play state, e.g. playing or paused."""
def device_state(self): # pylint: disable=too-many-return-statements
"""Device state, e.g. playing or paused."""
state = self._state.playback_state
ssm = SetStateMessage_pb2.SetStateMessage
if state is None:
return const.PLAY_STATE_IDLE
return const.DEVICE_STATE_IDLE
if state == ssm.Playing:
return const.PLAY_STATE_PLAYING
return const.DEVICE_STATE_PLAYING
if state == ssm.Paused:
return const.PLAY_STATE_PAUSED
return const.DEVICE_STATE_PAUSED
if state == ssm.Stopped:
return const.PLAY_STATE_STOPPED
return const.DEVICE_STATE_STOPPED
if state == ssm.Interrupted:
return const.PLAY_STATE_LOADING
# if state == SetStateMessage_pb2.Seeking
# return XXX
return const.DEVICE_STATE_LOADING
if state == ssm.Seeking:
return const.DEVICE_STATE_SEEKING

return const.PLAY_STATE_PAUSED
return const.DEVICE_STATE_PAUSED

@property
def title(self):
Expand Down Expand Up @@ -228,7 +229,7 @@ def position(self):
elapsed_time = self._state.metadata_field('elapsedTime')
if elapsed_time:
diff = (datetime.now() - self._state.timestamp).total_seconds()
if self.play_state == const.PLAY_STATE_PLAYING:
if self.device_state == const.DEVICE_STATE_PLAYING:
return int(elapsed_time + diff)
return int(elapsed_time)
return None
Expand Down
1 change: 1 addition & 0 deletions pyatv/mrp/protobuf/ProtocolMessage.proto
Expand Up @@ -37,6 +37,7 @@ message ProtocolMessage {
CRYPTO_PAIRING_MESSAGE = 34;
GAME_CONTROLLER_PROPERTIES_MESSAGE = 35;
SET_READY_STATE_MESSAGE = 36;
DEVICE_INFO_UPDATE_MESSAGE = 37;
SET_CONNECTION_STATE_MESSAGE = 38;
SET_HILITE_MODE_MESSAGE = 40;
WAKE_DEVICE_MESSAGE = 41;
Expand Down
24 changes: 14 additions & 10 deletions pyatv/mrp/protobuf/ProtocolMessage_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyatv/mrp/protobuf/ProtocolMessage_pb2.pyi
Expand Up @@ -69,6 +69,7 @@ class ProtocolMessage(google___protobuf___message___Message):
CRYPTO_PAIRING_MESSAGE = typing___cast(ProtocolMessage.Type, 34)
GAME_CONTROLLER_PROPERTIES_MESSAGE = typing___cast(ProtocolMessage.Type, 35)
SET_READY_STATE_MESSAGE = typing___cast(ProtocolMessage.Type, 36)
DEVICE_INFO_UPDATE_MESSAGE = typing___cast(ProtocolMessage.Type, 37)
SET_CONNECTION_STATE_MESSAGE = typing___cast(ProtocolMessage.Type, 38)
SET_HILITE_MODE_MESSAGE = typing___cast(ProtocolMessage.Type, 40)
WAKE_DEVICE_MESSAGE = typing___cast(ProtocolMessage.Type, 41)
Expand Down Expand Up @@ -109,6 +110,7 @@ class ProtocolMessage(google___protobuf___message___Message):
CRYPTO_PAIRING_MESSAGE = typing___cast(ProtocolMessage.Type, 34)
GAME_CONTROLLER_PROPERTIES_MESSAGE = typing___cast(ProtocolMessage.Type, 35)
SET_READY_STATE_MESSAGE = typing___cast(ProtocolMessage.Type, 36)
DEVICE_INFO_UPDATE_MESSAGE = typing___cast(ProtocolMessage.Type, 37)
SET_CONNECTION_STATE_MESSAGE = typing___cast(ProtocolMessage.Type, 38)
SET_HILITE_MODE_MESSAGE = typing___cast(ProtocolMessage.Type, 40)
WAKE_DEVICE_MESSAGE = typing___cast(ProtocolMessage.Type, 41)
Expand Down
1 change: 1 addition & 0 deletions pyatv/mrp/protobuf/__init__.py
Expand Up @@ -90,6 +90,7 @@
ProtocolMessage.CLIENT_UPDATES_CONFIG_MESSAGE: ClientUpdatesConfigMessage_pb2.clientUpdatesConfigMessage,
ProtocolMessage.CRYPTO_PAIRING_MESSAGE: CryptoPairingMessage_pb2.cryptoPairingMessage,
ProtocolMessage.DEVICE_INFO_MESSAGE: DeviceInfoMessage_pb2.deviceInfoMessage,
ProtocolMessage.DEVICE_INFO_UPDATE_MESSAGE: DeviceInfoMessage_pb2.deviceInfoMessage,
ProtocolMessage.GET_KEYBOARD_SESSION_MESSAGE: GetKeyboardSessionMessage_pb2.getKeyboardSessionMessage,
ProtocolMessage.KEYBOARD_MESSAGE: KeyboardMessage_pb2.keyboardMessage,
ProtocolMessage.NOTIFICATION_MESSAGE: NotificationMessage_pb2.notificationMessage,
Expand Down
9 changes: 9 additions & 0 deletions scripts/autogen_protobuf_extensions.py
Expand Up @@ -13,6 +13,9 @@
from collections import namedtuple


# New messages re-using inner message of another type
REUSED_MESSAGES = {'DEVICE_INFO_MESSAGE': 'DEVICE_INFO_UPDATE_MESSAGE'}

BASE_PACKAGE = 'pyatv.mrp.protobuf'
OUTPUT_TEMPLATE = """\"\"\"Simplified extension handling for protobuf messages.
Expand Down Expand Up @@ -122,6 +125,12 @@ def main():
'{0} = ProtocolMessage.{0}'.format(
info.const))

reused = REUSED_MESSAGES.get(info.const)
if reused:
extensions.append(
'ProtocolMessage.{0}: {1}.{2},'.format(
reused, info.module, info.accessor))

# Look for remaining messages
for module_name, message_name in extract_unreferenced_messages():
if message_name not in message_names:
Expand Down

0 comments on commit e3ac495

Please sign in to comment.