PlexKodiConnect/resources/lib/plexnet/nowplayingmanager.py
2018-09-30 13:16:17 +02:00

212 lines
7.8 KiB
Python

# Most of this is ported from Roku code and much of it is currently unused
# TODO: Perhaps remove unnecessary code
import time
import util
import urllib
import urlparse
import plexapp
import plexrequest
import callback
import http
class ServerTimeline(util.AttributeDict):
def reset(self):
self.expires = time.time() + 15
def isExpired(self):
return time.time() > self.get('expires', 0)
class TimelineData(util.AttributeDict):
def __init__(self, timelineType, *args, **kwargs):
util.AttributeDict.__init__(self, *args, **kwargs)
self.type = timelineType
self.state = "stopped"
self.item = None
self.choice = None
self.playQueue = None
self.controllable = util.AttributeDict()
self.controllableStr = None
self.attrs = util.AttributeDict()
# Set default controllable for all content. Other controllable aspects
# will be set based on the players content.
#
self.setControllable("playPause", True)
self.setControllable("stop", True)
def setControllable(self, name, isControllable):
if isControllable:
self.controllable[name] = ""
else:
if name in self.controllable:
del self.controllable[name]
self.controllableStr = None
def updateControllableStr(self):
if not self.controllableStr:
self.controllableStr = ""
prependComma = False
for name in self.controllable:
if prependComma:
self.controllableStr += ','
else:
prependComma = True
self.controllableStr += name
def toXmlAttributes(self, elem):
self.updateControllableStr()
elem.attrib["type"] = self.type
elem.attrib["start"] = self.state
elem.attrib["controllable"] = self.controllableStr
if self.item:
if self.item.duration:
elem.attrib['duration'] = self.item.duration
if self.item.ratingKey:
elem.attrib['ratingKey'] = self.item.ratingKey
if self.item.key:
elem.attrib['key'] = self.item.key
if self.item.container.address:
elem.attrib['containerKey'] = self.item.container.address
# Send the audio, video and subtitle choice if it's available
if self.choice:
for stream in ("audioStream", "videoStream", "subtitleStream"):
if self.choice.get(stream) and self.choice[stream].id:
elem.attrib[stream + "ID"] = self.choice[stream].id
server = self.item.getServer()
if server:
elem.attrib["machineIdentifier"] = server.uuid
if server.activeConnection:
parts = urlparse.uslparse(server.activeConnection.address)
elem.attrib["protocol"] = parts.scheme
elem.attrib["address"] = parts.netloc.split(':', 1)[0]
if ':' in parts.netloc:
elem.attrib["port"] = parts.netloc.split(':', 1)[-1]
elif parts.scheme == 'https':
elem.attrib["port"] = '443'
else:
elem.attrib["port"] = '80'
if self.playQueue:
elem.attrib["playQueueID"] = str(self.playQueue.id)
elem.attrib["playQueueItemID"] = str(self.playQueue.selectedId)
elem.attrib["playQueueVersion"] = str(self.playQueue.version)
for key, val in self.attrs.items():
elem.attrib[key] = val
class NowPlayingManager(object):
def __init__(self):
# Constants
self.NAVIGATION = "navigation"
self.FULLSCREEN_VIDEO = "fullScreenVideo"
self.FULLSCREEN_MUSIC = "fullScreenMusic"
self.FULLSCREEN_PHOTO = "fullScreenPhoto"
self.TIMELINE_TYPES = ["video", "music", "photo"]
# Members
self.serverTimelines = util.AttributeDict()
self.subscribers = util.AttributeDict()
self.pollReplies = util.AttributeDict()
self.timelines = util.AttributeDict()
self.location = self.NAVIGATION
self.textFieldName = None
self.textFieldContent = None
self.textFieldSecure = None
# Initialization
for timelineType in self.TIMELINE_TYPES:
self.timelines[timelineType] = TimelineData(timelineType)
def updatePlaybackState(self, timelineType, playerObject, state, time, playQueue=None, duration=0):
timeline = self.timelines[timelineType]
timeline.state = state
timeline.item = playerObject.item
timeline.choice = playerObject.choice
timeline.playQueue = playQueue
timeline.attrs["time"] = str(time)
timeline.duration = duration
# self.sendTimelineToAll()
self.sendTimelineToServer(timelineType, timeline, time)
def sendTimelineToServer(self, timelineType, timeline, time):
if not hasattr(timeline.item, 'getServer') or not timeline.item.getServer():
return
serverTimeline = self.getServerTimeline(timelineType)
# Only send timeline if it's the first, item changes, playstate changes or timer pops
itemsEqual = timeline.item and serverTimeline.item and timeline.item.ratingKey == serverTimeline.item.ratingKey
if itemsEqual and timeline.state == serverTimeline.state and not serverTimeline.isExpired():
return
serverTimeline.reset()
serverTimeline.item = timeline.item
serverTimeline.state = timeline.state
# Ignore sending timelines for multi part media with no duration
obj = timeline.choice
if obj and obj.part and obj.part.duration.asInt() == 0 and obj.media.parts and len(obj.media.parts) > 1:
util.WARN_LOG("Timeline not supported: the current part doesn't have a valid duration")
return
# It's possible with timers and in player seeking for the time to be greater than the
# duration, which causes a 400, so in that case we'll set the time to the duration.
duration = timeline.item.duration.asInt() or timeline.duration
if time > duration:
time = duration
params = util.AttributeDict()
params["time"] = time
params["duration"] = duration
params["state"] = timeline.state
params["guid"] = timeline.item.guid
params["ratingKey"] = timeline.item.ratingKey
params["url"] = timeline.item.url
params["key"] = timeline.item.key
params["containerKey"] = timeline.item.container.address
if timeline.playQueue:
params["playQueueItemID"] = timeline.playQueue.selectedId
path = "/:/timeline"
for paramKey in params:
if params[paramKey]:
path = http.addUrlParam(path, paramKey + "=" + urllib.quote(str(params[paramKey])))
request = plexrequest.PlexRequest(timeline.item.getServer(), path)
context = request.createRequestContext("timelineUpdate", callback.Callable(self.onTimelineResponse))
context.playQueue = timeline.playQueue
plexapp.APP.startRequest(request, context)
def getServerTimeline(self, timelineType):
if not self.serverTimelines.get(timelineType):
serverTL = ServerTimeline()
serverTL.reset()
self.serverTimelines[timelineType] = serverTL
return self.serverTimelines[timelineType]
def nowPlayingSetControllable(self, timelineType, name, isControllable):
self.timelines[timelineType].setControllable(name, isControllable)
def onTimelineResponse(self, request, response, context):
if not context.playQueue or not context.playQueue.refreshOnTimeline:
return
context.playQueue.refreshOnTimeline = False
context.playQueue.refresh(False)