jellyfin-kodi/resources/lib/websocket_client.py

311 lines
11 KiB
Python

# -*- coding: utf-8 -*-
#################################################################################################
import json
import logging
import threading
import websocket
import xbmc
import xbmcgui
import clientinfo
import downloadutils
import librarysync
import playlist
import userclient
from utils import window, settings, language as lang, JSONRPC
##################################################################################################
log = logging.getLogger("EMBY."+__name__)
##################################################################################################
class WebSocket_Client(threading.Thread):
_shared_state = {}
client = None
stopWebsocket = False
def __init__(self):
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
self.doUtils = downloadutils.DownloadUtils()
self.clientInfo = clientinfo.ClientInfo()
self.deviceId = self.clientInfo.getDeviceId()
self.librarySync = librarysync.LibrarySync()
threading.Thread.__init__(self)
def sendProgressUpdate(self, data):
log.debug("sendProgressUpdate")
try:
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
log.debug("Message data: %s" % messageString)
except Exception as e:
log.exception(e)
def on_message(self, ws, message):
result = json.loads(message)
messageType = result['MessageType']
data = result['Data']
dialog = xbmcgui.Dialog()
if messageType not in ('SessionEnded'):
# Mute certain events
log.info("Message: %s" % message)
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
command = data['PlayCommand']
pl = playlist.Playlist()
if command == "PlayNow":
dialog.notification(
heading=lang(29999),
message="%s %s" % (len(itemIds), lang(33004)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
startat = data.get('StartPositionTicks', 0)
pl.playAll(itemIds, startat)
elif command == "PlayNext":
dialog.notification(
heading=lang(29999),
message="%s %s" % (len(itemIds), lang(33005)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
newplaylist = pl.modifyPlaylist(itemIds)
player = xbmc.Player()
if not player.isPlaying():
# Only start the playlist if nothing is playing
player.play(newplaylist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
player = xbmc.Player()
actions = {
'Stop': player.stop,
'Unpause': player.pause,
'Pause': player.pause,
'NextTrack': player.playnext,
'PreviousTrack': player.playprevious,
'Seek': player.seekTime
}
action = actions[command]
if command == "Seek":
seekto = data['SeekPositionTicks']
seektime = seekto / 10000000.0
action(seektime)
log.info("Seek to %s." % seektime)
else:
action()
log.info("Command: %s completed." % command)
window('emby_command', value="true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList']
self.librarySync.triage_items("userdata", userdata_list)
elif messageType == "LibraryChanged":
librarySync = self.librarySync
processlist = {
'added': data['ItemsAdded'],
'update': data['ItemsUpdated'],
'remove': data['ItemsRemoved']
}
for action in processlist:
librarySync.triage_items(action, processlist[action])
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data['Arguments']
if command in ('Mute', 'Unmute', 'SetVolume',
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
player = xbmc.Player()
# These commands need to be reported back
if command == "Mute":
xbmc.executebuiltin('Mute')
elif command == "Unmute":
xbmc.executebuiltin('Mute')
elif command == "SetVolume":
volume = arguments['Volume']
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
player.setAudioStream(index - 1)
elif command == "SetSubtitleStreamIndex":
embyindex = int(arguments['Index'])
currentFile = player.getPlayingFile()
mapping = window('emby_%s.indexMapping' % currentFile)
if mapping:
externalIndex = json.loads(mapping)
# If there's external subtitles added via playbackutils
for index in externalIndex:
if externalIndex[index] == embyindex:
player.setSubtitleStream(int(index))
break
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(external + embyindex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(index - audioTracks - 1)
# Let service know
window('emby_command', value="true")
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
dialog.notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
elif command == "SendString":
params = {
'text': arguments['String'],
'done': False
}
result = JSONRPC("Input.SendText").execute(params)
elif command in ("MoveUp", "MoveDown", "MoveRight", "MoveLeft"):
# Commands that should wake up display
actions = {
'MoveUp': "Input.Up",
'MoveDown': "Input.Down",
'MoveRight': "Input.Right",
'MoveLeft': "Input.Left"
}
result = JSONRPC(actions[command]).execute()
elif command == "GoHome":
result = JSONRPC("GUI.ActivateWindow").execute({"window":"home"})
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)',
}
action = builtin.get(command)
if action:
xbmc.executebuiltin(action)
elif messageType == "ServerRestarting":
if settings('supressRestartMsg') == "true":
dialog.notification(
heading=lang(29999),
message=lang(33006),
icon="special://home/addons/plugin.video.emby/icon.png")
elif messageType == "UserConfigurationUpdated":
# Update user data set in userclient
userclient.UserClient().userSettings = data
self.librarySync.refresh_views = True
def on_close(self, ws):
log.debug("Closed.")
def on_open(self, ws):
self.doUtils.postCapabilities(self.deviceId)
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
log.debug("Error: %s" % error)
def run(self):
loglevel = int(window('emby_logLevel'))
# websocket.enableTrace(True)
userId = window('emby_currUser')
server = window('emby_server%s' % userId)
token = window('emby_accessToken%s' % userId)
# Get the appropriate prefix for the websocket
if "https" in server:
server = server.replace('https', "wss")
else:
server = server.replace('http', "ws")
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
log.info("websocket url: %s" % websocket_url)
self.client = websocket.WebSocketApp(websocket_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close)
self.client.on_open = self.on_open
log.warn("----===## Starting WebSocketClient ##===----")
while not self.monitor.abortRequested():
self.client.run_forever(ping_interval=10)
if self.stopWebsocket:
break
if self.monitor.waitForAbort(5):
# Abort was requested, exit
break
log.warn("##===---- WebSocketClient Stopped ----===##")
def stopClient(self):
self.stopWebsocket = True
self.client.close()
log.info("Stopping thread.")