Update timeline to return an active timeline object w/ attributes (#572)

* timeline() now returns ClientTimeline objects

* timeline() creates and returns ClientTimeline objects with associated attributes and caching
* Refactor isPlayingMedia to use the new attributes and fix it's default value

* Clarify docstrings

* Remove default param in timeline call & fix docstring typo

* return empty list if `timelines()` comes back empty

Web clients can occasionally return no timelines if no media has been played on them or if nothing has played for a while, this prevents errors in those cases.

* typo

* Workaround for unresponsive clients

* Use sendCommand rather than timelines() for PTP workaround

* Remove workaround, set timeline's wait default to 1

* set timelines() wait default to 0, document buggy behavior

* Use ClientTimeline.key for consistency

Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>

* cast playQueue's IDs as int

Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>

* Add audio attribs & make casts bool from int where it makes sense.

Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>
This commit is contained in:
Ryan Meek 2020-10-02 12:33:53 -04:00 committed by GitHub
parent 163d94d249
commit f9f3535751
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -69,6 +69,8 @@ class PlexClient(PlexObject):
self._proxyThroughServer = False
self._commandId = 0
self._last_call = 0
self._timeline_cache = []
self._timeline_cache_timestamp = 0
if not any([data is not None, initpath, baseurl, token]):
self._baseurl = CONFIG.get('auth.client_baseurl', 'http://localhost:32433')
self._token = logfilter.add_secret(CONFIG.get('auth.client_token'))
@ -199,7 +201,7 @@ class PlexClient(PlexObject):
self._last_call = t
elif t - self._last_call >= 80 and self.product in ('ptp', 'Plex Media Player'):
self._last_call = t
self.timeline(wait=0)
self.sendCommand(ClientTimeline.key, wait=0)
params['commandID'] = self._nextCommandId()
key = '/player/%s%s' % (command, utils.joinArgs(params))
@ -540,20 +542,68 @@ class PlexClient(PlexObject):
# -------------------
# Timeline Commands
def timeline(self, wait=1):
""" Poll the current timeline and return the XML response. """
return self.sendCommand('timeline/poll', wait=wait)
def timelines(self, wait=0):
"""Poll the client's timelines, create, and return timeline objects.
Some clients may not always respond to timeline requests, believe this
to be a Plex bug.
"""
t = time.time()
if t - self._timeline_cache_timestamp > 1:
self._timeline_cache_timestamp = t
timelines = self.sendCommand(ClientTimeline.key, wait=wait) or []
self._timeline_cache = [ClientTimeline(self, data) for data in timelines]
def isPlayingMedia(self, includePaused=False):
""" Returns True if any media is currently playing.
return self._timeline_cache
@property
def timeline(self):
"""Returns the active timeline object."""
return next((x for x in self.timelines() if x.state != 'stopped'), None)
def isPlayingMedia(self, includePaused=True):
"""Returns True if any media is currently playing.
Parameters:
includePaused (bool): Set True to treat currently paused items
as playing (optional; default True).
as playing (optional; default True).
"""
for mediatype in self.timeline(wait=0):
if mediatype.get('state') == 'playing':
return True
if includePaused and mediatype.get('state') == 'paused':
return True
return False
state = getattr(self.timeline, "state", None)
return bool(state == 'playing' or (includePaused and state == 'paused'))
class ClientTimeline(PlexObject):
"""Get the timeline's attributes."""
key = 'timeline/poll'
def _loadData(self, data):
self._data = data
self.address = data.attrib.get('address')
self.audioStreamId = utils.cast(int, data.attrib.get('audioStreamId'))
self.autoPlay = utils.cast(bool, data.attrib.get('autoPlay'))
self.containerKey = data.attrib.get('containerKey')
self.controllable = data.attrib.get('controllable')
self.duration = utils.cast(int, data.attrib.get('duration'))
self.itemType = data.attrib.get('itemType')
self.key = data.attrib.get('key')
self.location = data.attrib.get('location')
self.machineIdentifier = data.attrib.get('machineIdentifier')
self.partCount = utils.cast(int, data.attrib.get('partCount'))
self.partIndex = utils.cast(int, data.attrib.get('partIndex'))
self.playQueueID = utils.cast(int, data.attrib.get('playQueueID'))
self.playQueueItemID = utils.cast(int, data.attrib.get('playQueueItemID'))
self.playQueueVersion = utils.cast(int, data.attrib.get('playQueueVersion'))
self.port = utils.cast(int, data.attrib.get('port'))
self.protocol = data.attrib.get('protocol')
self.providerIdentifier = data.attrib.get('providerIdentifier')
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.repeat = utils.cast(bool, data.attrib.get('repeat'))
self.seekRange = data.attrib.get('seekRange')
self.shuffle = utils.cast(bool, data.attrib.get('shuffle'))
self.state = data.attrib.get('state')
self.subtitleColor = data.attrib.get('subtitleColor')
self.subtitlePosition = data.attrib.get('subtitlePosition')
self.subtitleSize = utils.cast(int, data.attrib.get('subtitleSize'))
self.time = utils.cast(int, data.attrib.get('time'))
self.type = data.attrib.get('type')
self.volume = utils.cast(int, data.attrib.get('volume'))