# -*- 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().get_user(data) self.librarySync.refresh_views = True elif messageType == "ServerShuttingDown": # Server went offline window('emby_online', value="false") 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.")