mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-25 18:26:15 +00:00
362 lines
11 KiB
Python
362 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import division, absolute_import, print_function, unicode_literals
|
|
|
|
#################################################################################################
|
|
|
|
import binascii
|
|
import json
|
|
import threading
|
|
|
|
import xbmc
|
|
|
|
from . import connect
|
|
from . import player
|
|
from .client import get_device_id
|
|
from .objects import PlaylistWorker, on_play, on_update, special_listener
|
|
from .helper import translate, settings, window, dialog, api, JSONRPC
|
|
from .helper.utils import JsonDebugPrinter
|
|
from .jellyfin import Jellyfin
|
|
from .helper import LazyLogger
|
|
|
|
#################################################################################################
|
|
|
|
LOG = LazyLogger(__name__)
|
|
|
|
#################################################################################################
|
|
|
|
|
|
class Monitor(xbmc.Monitor):
|
|
|
|
servers = []
|
|
sleep = False
|
|
|
|
def __init__(self):
|
|
|
|
self.player = player.Player()
|
|
self.device_id = get_device_id()
|
|
self.listener = Listener(self)
|
|
self.listener.start()
|
|
xbmc.Monitor.__init__(self)
|
|
|
|
def onScanStarted(self, library):
|
|
LOG.info("-->[ kodi scan/%s ]", library)
|
|
|
|
def onScanFinished(self, library):
|
|
LOG.info("--<[ kodi scan/%s ]", library)
|
|
|
|
def onNotification(self, sender, method, data):
|
|
|
|
if sender.lower() not in (
|
|
"plugin.video.jellyfin",
|
|
"xbmc",
|
|
"upnextprovider.signal",
|
|
):
|
|
return
|
|
|
|
if sender == "plugin.video.jellyfin":
|
|
method = method.split(".")[1]
|
|
|
|
if method not in (
|
|
"ReportProgressRequested",
|
|
"LoadServer",
|
|
"AddUser",
|
|
"PlayPlaylist",
|
|
"Play",
|
|
"Playstate",
|
|
"GeneralCommand",
|
|
):
|
|
return
|
|
|
|
data = json.loads(data)[0]
|
|
|
|
elif sender.startswith("upnextprovider"):
|
|
LOG.info("Attempting to play the next episode via upnext")
|
|
method = method.split(".", 1)[1]
|
|
|
|
if method not in ("plugin.video.jellyfin_play_action",):
|
|
LOG.info("Received invalid upnext method: %s", method)
|
|
return
|
|
|
|
data = json.loads(data)
|
|
method = "Play"
|
|
|
|
if data:
|
|
data = json.loads(binascii.unhexlify(data[0]))
|
|
else:
|
|
if method not in (
|
|
"Player.OnPlay",
|
|
"VideoLibrary.OnUpdate",
|
|
"Player.OnAVChange",
|
|
):
|
|
|
|
"""We have to clear the playlist if it was stopped before it has been played completely.
|
|
Otherwise the next played item will be added the previous queue.
|
|
"""
|
|
if method == "Player.OnStop":
|
|
xbmc.sleep(
|
|
3000
|
|
) # let's wait for the player, so we don't clear the canceled playlist by mistake.
|
|
|
|
if xbmc.getCondVisibility(
|
|
"!Player.HasMedia + !Window.IsVisible(busydialog)"
|
|
):
|
|
|
|
xbmc.executebuiltin("Playlist.Clear")
|
|
LOG.info("[ playlist ] cleared")
|
|
|
|
return
|
|
|
|
data = json.loads(data)
|
|
|
|
LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data))
|
|
|
|
if self.sleep:
|
|
LOG.info("System.OnSleep detected, ignore monitor request.")
|
|
|
|
return
|
|
|
|
try:
|
|
if not data.get("ServerId"):
|
|
server = Jellyfin()
|
|
else:
|
|
if method != "LoadServer" and data["ServerId"] not in self.servers:
|
|
|
|
try:
|
|
connect.Connect().register(data["ServerId"])
|
|
self.server_instance(data["ServerId"])
|
|
except Exception as error:
|
|
|
|
LOG.exception(error)
|
|
dialog("ok", "{jellyfin}", translate(33142))
|
|
|
|
return
|
|
|
|
server = Jellyfin(data["ServerId"])
|
|
except Exception as error:
|
|
LOG.exception(error)
|
|
server = Jellyfin()
|
|
|
|
server = server.get_client()
|
|
|
|
if method == "Play":
|
|
|
|
items = server.jellyfin.get_items(data["ItemIds"])
|
|
|
|
PlaylistWorker(
|
|
data.get("ServerId"),
|
|
items,
|
|
data["PlayCommand"] == "PlayNow",
|
|
data.get("StartPositionTicks", 0),
|
|
data.get("AudioStreamIndex"),
|
|
data.get("SubtitleStreamIndex"),
|
|
).start()
|
|
|
|
# TODO no clue if this is called by anything
|
|
elif method == "PlayPlaylist":
|
|
|
|
server.jellyfin.post_session(
|
|
server.config.data["app.session"],
|
|
"Playing",
|
|
{
|
|
"PlayCommand": "PlayNow",
|
|
"ItemIds": data["Id"],
|
|
"StartPositionTicks": 0,
|
|
},
|
|
)
|
|
|
|
elif method in ("ReportProgressRequested", "Player.OnAVChange"):
|
|
self.player.report_playback(data.get("Report", True))
|
|
|
|
elif method == "Playstate":
|
|
self.playstate(data)
|
|
|
|
elif method == "GeneralCommand":
|
|
self.general_commands(data)
|
|
|
|
elif method == "LoadServer":
|
|
self.server_instance(data["ServerId"])
|
|
|
|
elif method == "AddUser":
|
|
server.jellyfin.session_add_user(
|
|
server.config.data["app.session"], data["Id"], data["Add"]
|
|
)
|
|
self.additional_users(server)
|
|
|
|
elif method == "Player.OnPlay":
|
|
on_play(data, server)
|
|
|
|
elif method == "VideoLibrary.OnUpdate":
|
|
on_update(data, server)
|
|
|
|
def server_instance(self, server_id=None):
|
|
|
|
server = Jellyfin(server_id).get_client()
|
|
session = server.jellyfin.get_device(self.device_id)
|
|
server.config.data["app.session"] = session[0]["Id"]
|
|
|
|
if server_id is not None:
|
|
self.servers.append(server_id)
|
|
elif settings("additionalUsers"):
|
|
|
|
users = settings("additionalUsers").split(",")
|
|
all_users = server.jellyfin.get_users()
|
|
|
|
for additional in users:
|
|
for user in all_users:
|
|
|
|
if user["Name"].lower() in additional.lower():
|
|
server.jellyfin.session_add_user(
|
|
server.config.data["app.session"], user["Id"], True
|
|
)
|
|
|
|
self.additional_users(server)
|
|
|
|
def additional_users(self, server):
|
|
"""Setup additional users images."""
|
|
for i in range(10):
|
|
window("JellyfinAdditionalUserImage.%s" % i, clear=True)
|
|
|
|
try:
|
|
session = server.jellyfin.get_device(self.device_id)
|
|
except Exception as error:
|
|
LOG.exception(error)
|
|
|
|
return
|
|
|
|
for index, user in enumerate(session[0]["AdditionalUsers"]):
|
|
|
|
info = server.jellyfin.get_user(user["UserId"])
|
|
image = api.API(info, server.config.data["auth.server"]).get_user_artwork(
|
|
user["UserId"]
|
|
)
|
|
window("JellyfinAdditionalUserImage.%s" % index, image)
|
|
window("JellyfinAdditionalUserPosition.%s" % user["UserId"], str(index))
|
|
|
|
def playstate(self, data):
|
|
"""Jellyfin playstate updates."""
|
|
command = data["Command"]
|
|
actions = {
|
|
"Stop": self.player.stop,
|
|
"Unpause": self.player.pause,
|
|
"Pause": self.player.pause,
|
|
"PlayPause": self.player.pause,
|
|
"NextTrack": self.player.playnext,
|
|
"PreviousTrack": self.player.playprevious,
|
|
}
|
|
if command == "Seek":
|
|
|
|
if self.player.isPlaying():
|
|
|
|
seektime = data["SeekPositionTicks"] / 10000000.0
|
|
self.player.seekTime(seektime)
|
|
LOG.info("[ seek/%s ]", seektime)
|
|
|
|
elif command in actions:
|
|
|
|
actions[command]()
|
|
LOG.info("[ command/%s ]", command)
|
|
|
|
def general_commands(self, data):
|
|
"""General commands from Jellyfin to control the Kodi interface."""
|
|
command = data["Name"]
|
|
args = data["Arguments"]
|
|
|
|
if command in (
|
|
"Mute",
|
|
"Unmute",
|
|
"SetVolume",
|
|
"SetSubtitleStreamIndex",
|
|
"SetAudioStreamIndex",
|
|
"SetRepeatMode",
|
|
):
|
|
|
|
if command in ["Mute", "Unmute"]:
|
|
xbmc.executebuiltin("Mute")
|
|
elif command == "SetAudioStreamIndex":
|
|
self.player.set_audio_subs(args["Index"])
|
|
elif command == "SetRepeatMode":
|
|
xbmc.executebuiltin("xbmc.PlayerControl(%s)" % args["RepeatMode"])
|
|
elif command == "SetSubtitleStreamIndex":
|
|
self.player.set_audio_subs(None, args["Index"])
|
|
|
|
elif command == "SetVolume":
|
|
xbmc.executebuiltin("SetVolume(%s[,showvolumebar])" % args["Volume"])
|
|
# Kodi needs a bit of time to update its current status
|
|
xbmc.sleep(500)
|
|
self.player.report_playback()
|
|
|
|
elif command == "DisplayMessage":
|
|
dialog(
|
|
"notification",
|
|
heading=args["Header"],
|
|
message=args["Text"],
|
|
icon="{jellyfin}",
|
|
time=int(settings("displayMessage")) * 1000,
|
|
)
|
|
|
|
elif command == "SendString":
|
|
JSONRPC("Input.SendText").execute({"text": args["String"], "done": False})
|
|
|
|
elif command == "GoHome":
|
|
JSONRPC("GUI.ActivateWindow").execute({"window": "home"})
|
|
|
|
elif command == "Guide":
|
|
JSONRPC("GUI.ActivateWindow").execute({"window": "tvguide"})
|
|
|
|
elif command in ("MoveUp", "MoveDown", "MoveRight", "MoveLeft"):
|
|
actions = {
|
|
"MoveUp": "Input.Up",
|
|
"MoveDown": "Input.Down",
|
|
"MoveRight": "Input.Right",
|
|
"MoveLeft": "Input.Left",
|
|
}
|
|
JSONRPC(actions[command]).execute()
|
|
|
|
else:
|
|
builtin = {
|
|
"ToggleFullscreen": "Action(FullScreen)",
|
|
"ToggleOsdMenu": "Action(OSD)",
|
|
"ToggleContextMenu": "Action(ContextMenu)",
|
|
"Select": "Action(Select)",
|
|
"Back": "Action(back)",
|
|
"PageUp": "Action(PageUp)",
|
|
"NextLetter": "Action(NextLetter)",
|
|
"GoToSearch": "VideoLibrary.Search",
|
|
"GoToSettings": "ActivateWindow(Settings)",
|
|
"PageDown": "Action(PageDown)",
|
|
"PreviousLetter": "Action(PrevLetter)",
|
|
"TakeScreenshot": "TakeScreenshot",
|
|
"ToggleMute": "Mute",
|
|
"VolumeUp": "Action(VolumeUp)",
|
|
"VolumeDown": "Action(VolumeDown)",
|
|
}
|
|
if command in builtin:
|
|
xbmc.executebuiltin(builtin[command])
|
|
|
|
|
|
class Listener(threading.Thread):
|
|
|
|
stop_thread = False
|
|
|
|
def __init__(self, monitor):
|
|
self.monitor = monitor
|
|
|
|
threading.Thread.__init__(self)
|
|
|
|
def run(self):
|
|
"""Detect the resume dialog for widgets.
|
|
Detect external players.
|
|
"""
|
|
LOG.info("--->[ listener ]")
|
|
|
|
while not self.stop_thread:
|
|
special_listener()
|
|
|
|
if self.monitor.waitForAbort(0.5):
|
|
# Abort was requested while waiting. We should exit
|
|
break
|
|
|
|
LOG.info("---<[ listener ]")
|
|
|
|
def stop(self):
|
|
self.stop_thread = True
|