jellyfin-mpv-shim/timeline.py

160 lines
5.6 KiB
Python

import logging
import requests
import threading
import time
try:
from xml.etree import cElementTree as et
except:
from xml.etree import ElementTree as et
from io import BytesIO
from conf import settings
from player import playerManager
from subscribers import remoteSubscriberManager
from utils import Timer
log = logging.getLogger("timeline")
class TimelineManager(threading.Thread):
def __init__(self):
self.currentItems = {}
self.currentStates = {}
self.idleTimer = Timer()
self.subTimer = Timer()
self.serverTimer = Timer()
self.stopped = False
self.halt = False
threading.Thread.__init__(self)
def stop(self):
self.halt = True
self.join()
def run(self):
while not self.halt:
if playerManager._player and playerManager._video:
if not playerManager.is_paused():
self.SendTimelineToSubscribers()
playerManager.update()
self.idleTimer.restart()
time.sleep(1)
def SendTimelineToSubscribers(self):
log.debug("TimelineManager::SendTimelineToSubscribers updating all subscribers")
for sub in list(remoteSubscriberManager.subscribers.values()):
self.SendTimelineToSubscriber(sub)
def SendTimelineToSubscriber(self, subscriber):
if subscriber.url == "":
return
timelineXML = self.GetCurrentTimeLinesXML(subscriber)
url = "%s/:/timeline" % subscriber.url
log.debug("TimelineManager::SendTimelineToSubscriber sending timeline to %s" % url)
tree = et.ElementTree(timelineXML)
tmp = BytesIO()
tree.write(tmp, encoding="utf-8", xml_declaration=True)
tmp.seek(0)
xmlData = tmp.read()
# TODO: Abstract this into a utility function and add other X-Plex-XXX fields
requests.post(url, data=xmlData, headers={
"Content-Type": "application/x-www-form-urlencoded",
"Connection": "keep-alive",
"Content-Range": "bytes 0-/-1",
"X-Plex-Client-Identifier": settings.client_uuid
})
def WaitForTimeline(self, subscriber):
time.sleep(1)
return self.GetCurrentTimeLinesXML(subscriber)
def GetCurrentTimeLinesXML(self, subscriber):
tlines = self.GetCurrentTimeline()
#
# Only "video" is supported right now
#
mediaContainer = et.Element("MediaContainer")
if subscriber.commandID is not None:
mediaContainer.set("commandID", str(subscriber.commandID))
mediaContainer.set("location", tlines["location"])
lineEl = et.Element("Timeline")
for key, value in list(tlines.items()):
lineEl.set(key, str(value))
mediaContainer.append(lineEl)
return mediaContainer
def GetCurrentTimeline(self):
# https://github.com/plexinc/plex-home-theater-public/blob/pht-frodo/plex/Client/PlexTimelineManager.cpp#L142
# Note: location is set to "" to avoid pop-up of navigation menu. This may be abuse of the API.
options = {
"location": "",
"state": playerManager.get_state(),
"type": "video"
}
controllable = []
video = playerManager._video
player = playerManager._player
if video and not player.playback_abort:
media = playerManager._video.parent
options["location"] = "fullScreenVideo"
options["time"] = player.playback_time * 1e3
if player.sub != 'no':
options["subtitleStreamID"] = video.subtitle_uid.get(player.sub, '')
if player.audio != 'no':
options["audioStreamID"] = video.audio_uid.get(player.audio, '')
options["ratingKey"] = video.get_video_attr("ratingKey")
options["key"] = video.get_video_attr("key")
options["containerKey"] = video.get_video_attr("key")
options["guid"] = video.get_video_attr("guid")
options["duration"] = video.get_video_attr("duration", "0")
options["address"] = media.path.hostname
options["protocol"] = media.path.scheme
options["port"] = media.path.port
options["machineIdentifier"] = media.get_machine_identifier()
options["seekRange"] = "0-%s" % options["duration"]
controllable.append("playPause")
controllable.append("stop")
controllable.append("stepBack")
controllable.append("stepForward")
controllable.append("subtitleStream")
controllable.append("audioStream")
controllable.append("seekTo")
# If the duration is unknown, disable seeking
if options["duration"] == "0":
options.pop("duration")
options.pop("seekRange")
controllable.remove("seekTo")
# Volume control is enabled only if output isn't HDMI,
# although technically I'm pretty sure we can still control
# the volume even if the output is hdmi...
if settings.audio_output != "hdmi":
controllable.append("volume")
options["volume"] = str(playerManager.get_volume(percent=True)*100 or 0)
options["controllable"] = ",".join(controllable)
else:
options["time"] = 0
return options
timelineManager = TimelineManager()