jellyfin-kodi/resources/lib/WebSocketClient.py

305 lines
13 KiB
Python

# -*- coding: utf-8 -*-
import json
import threading
import websocket
import logging
#################################################################################################
# WebSocket Client thread
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from PlaybackUtils import PlaybackUtils
from LibrarySync import LibrarySync
from WriteKodiVideoDB import WriteKodiVideoDB
from WriteKodiMusicDB import WriteKodiMusicDB
logging.basicConfig()
class WebSocketThread(threading.Thread):
_shared_state = {}
doUtils = DownloadUtils()
clientInfo = ClientInformation()
KodiMonitor = KodiMonitor.Kodi_Monitor()
addonName = clientInfo.getAddonName()
client = None
keepRunning = True
def __init__(self, *args):
self.__dict__ = self._shared_state
threading.Thread.__init__(self, *args)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def sendProgressUpdate(self, data):
self.logMsg("sendProgressUpdate", 1)
if self.client:
try:
# Send progress update
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
self.logMsg("Message data: %s" % messageString, 2)
except Exception as e:
self.logMsg("Exception: %s" % e, 1)
def stopClient(self):
# stopping the client is tricky, first set keep_running to false and then trigger one
# more message by requesting one SessionsStart message, this causes the
# client to receive the message and then exit
if self.client:
self.logMsg("Stopping Client")
self.keepRunning = False
self.client.keep_running = False
self.client.close()
self.logMsg("Stopping Client : KeepRunning set to False")
else:
self.logMsg("Stopping Client NO Object ERROR")
def on_message(self, ws, message):
WINDOW = xbmcgui.Window(10000)
self.logMsg("Message: %s" % message, 1)
result = json.loads(message)
messageType = result['MessageType']
data = result.get("Data")
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
playCommand = data['PlayCommand']
if "PlayNow" in playCommand:
startPositionTicks = data.get('StartPositionTicks', 0)
xbmc.executebuiltin("Dialog.Close(all,true)")
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks)
elif "PlayNext" in playCommand:
xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds))
playlist = PlaybackUtils().AddToPlaylist(itemIds)
if not xbmc.Player().isPlaying():
xbmc.Player().play(playlist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
if "Stop" in command:
self.logMsg("Playback Stopped.", 1)
xbmc.Player().stop()
elif "Unpause" in command:
self.logMsg("Playback unpaused.", 1)
xbmc.Player().pause()
elif "Pause" in command:
self.logMsg("Playback paused.", 1)
xbmc.Player().pause()
elif "NextTrack" in command:
self.logMsg("Playback next track.", 1)
xbmc.Player().playnext()
elif "PreviousTrack" in command:
self.logMsg("Playback previous track.", 1)
xbmc.Player().playprevious()
elif "Seek" in command:
seekPositionTicks = data['SeekPositionTicks']
seekTime = seekPositionTicks / 10000000.0
self.logMsg("Seek to %s" % seekTime, 1)
xbmc.Player().seekTime(seekTime)
# Report playback
WINDOW.setProperty('commandUpdate', "true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdataList = data['UserDataList']
self.logMsg("Message: Doing UserDataChanged: UserDataList: %s" % userdataList, 1)
LibrarySync().user_data_update(userdataList)
elif messageType == "LibraryChanged":
# Library items
itemsRemoved = data.get("ItemsRemoved")
itemsAdded = data.get("ItemsAdded")
itemsUpdated = data.get("ItemsUpdated")
itemsToUpdate = itemsAdded + itemsUpdated
self.logMsg("Message: WebSocket LibraryChanged: Items Added: %s" % itemsAdded, 1)
self.logMsg("Message: WebSocket LibraryChanged: Items Updated: %s" % itemsUpdated, 1)
self.logMsg("Message: WebSocket LibraryChanged: Items Removed: %s" % itemsRemoved, 1)
LibrarySync().remove_items(itemsRemoved)
LibrarySync().update_items(itemsToUpdate)
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data.get("Arguments")
if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
# 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 == "SetSubtitleStreamIndex":
# Emby merges audio and subtitle index together
xbmcplayer = xbmc.Player()
currentFile = xbmcplayer.getPlayingFile()
embyIndex = int(arguments['Index'])
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
externalIndex = json.loads(mapping)
if externalIndex:
# If there's external subtitles added via PlaybackUtils
for index in externalIndex:
if externalIndex[index] == embyIndex:
xbmcplayer.setSubtitleStream(int(index))
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
xbmcplayer.setSubtitleStream(external + embyIndex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(xbmcplayer.getAvailableAudioStreams())
xbmcplayer.setSubtitleStream(index - audioTracks - 1)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
xbmc.Player().setAudioStream(index - 1)
# Report playback
WINDOW.setProperty('commandUpdate', "true")
else:
# GUI commands
if command == "ToggleFullscreen":
xbmc.executebuiltin('Action(FullScreen)')
elif command == "ToggleOsdMenu":
xbmc.executebuiltin('Action(OSD)')
elif command == "MoveUp":
xbmc.executebuiltin('Action(Up)')
elif command == "MoveDown":
xbmc.executebuiltin('Action(Down)')
elif command == "MoveLeft":
xbmc.executebuiltin('Action(Left)')
elif command == "MoveRight":
xbmc.executebuiltin('Action(Right)')
elif command == "Select":
xbmc.executebuiltin('Action(Select)')
elif command == "Back":
xbmc.executebuiltin('Action(back)')
elif command == "ToggleContextMenu":
xbmc.executebuiltin('Action(ContextMenu)')
elif command == "GoHome":
xbmc.executebuiltin('ActivateWindow(Home)')
elif command == "PageUp":
xbmc.executebuiltin('Action(PageUp)')
elif command == "NextLetter":
xbmc.executebuiltin('Action(NextLetter)')
elif command == "GoToSearch":
xbmc.executebuiltin('VideoLibrary.Search')
elif command == "GoToSettings":
xbmc.executebuiltin('ActivateWindow(Settings)')
elif command == "PageDown":
xbmc.executebuiltin('Action(PageDown)')
elif command == "PreviousLetter":
xbmc.executebuiltin('Action(PrevLetter)')
elif command == "TakeScreenshot":
xbmc.executebuiltin('TakeScreenshot')
elif command == "ToggleMute":
xbmc.executebuiltin('Mute')
elif command == "VolumeUp":
xbmc.executebuiltin('Action(VolumeUp)')
elif command == "VolumeDown":
xbmc.executebuiltin('Action(VolumeDown)')
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(header, text, icon="special://home/addons/plugin.video.emby/icon.png", time=4000)
elif command == "SendString":
string = arguments['String']
text = '{"jsonrpc": "2.0", "method": "Input.SendText", "params": { "text": "%s", "done": false }, "id": 0}' % string
result = xbmc.executeJSONRPC(text)
else:
self.logMsg("Unknown command.", 1)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification("Emby server", "Server is restarting.", icon="special://home/addons/plugin.video.emby/icon.png")
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 1)
#raise
def on_close(self, ws):
self.logMsg("Closed", 2)
def on_open(self, ws):
deviceId = ClientInformation().getMachineId()
self.doUtils.postCapabilities(deviceId)
def run(self):
WINDOW = xbmcgui.Window(10000)
logLevel = int(WINDOW.getProperty('getLogLevel'))
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
token = WINDOW.getProperty('accessToken%s' % username)
deviceId = ClientInformation().getMachineId()
'''if (logLevel == 2):
websocket.enableTrace(True)'''
# Get the appropriate prefix for websocket
if "https" in server:
server = server.replace('https', 'wss')
else:
server = server.replace('http', 'ws')
websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
self.logMsg("websocket URL: %s" % websocketUrl)
self.client = websocket.WebSocketApp(websocketUrl,
on_message = self.on_message,
on_error = self.on_error,
on_close = self.on_close)
self.client.on_open = self.on_open
while self.keepRunning:
self.client.run_forever()
if self.keepRunning:
self.logMsg("Client Needs To Restart", 2)
if self.KodiMonitor.waitForAbort(5):
break
self.logMsg("Thread Exited", 1)