# -*- coding: utf-8 -*- ################################################################################################# import json import logging import threading import websocket import xbmc import clientinfo import downloadutils import librarysync import playlist import userclient from utils import window, settings, dialog, language as lang, JSONRPC ################################################################################################## log = logging.getLogger("EMBY."+__name__) ################################################################################################## class WebSocket_Client(threading.Thread): _shared_state = {} _client = None _stop_websocket = False def __init__(self): self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() self.doutils = downloadutils.DownloadUtils() self.client_info = clientinfo.ClientInfo() self.device_id = self.client_info.get_device_id() self.library_sync = librarysync.LibrarySync() threading.Thread.__init__(self) def send_progress_update(self, data): log.debug("sendProgressUpdate") try: message = { 'MessageType': "ReportPlaybackProgress", 'Data': data } message_str = json.dumps(message) self._client.send(message_str) log.debug("Message data: %s", message_str) except Exception as error: log.exception(error) def on_message(self, ws, message): result = json.loads(message) message_type = result['MessageType'] data = result['Data'] if message_type not in ('SessionEnded'): # Mute certain events log.info("Message: %s" % message) if message_type == "Play": # A remote control play command has been sent from the server. self._play_(data) elif message_type == "Playstate": # A remote control update playstate command has been sent from the server. self._playstate_(data) elif message_type == "UserDataChanged": # A user changed their personal rating for an item, or their playstate was updated userdata_list = data['UserDataList'] self.library_sync.triage_items("userdata", userdata_list) elif message_type == "LibraryChanged": librarySync = self.library_sync processlist = { 'added': data['ItemsAdded'], 'update': data['ItemsUpdated'], 'remove': data['ItemsRemoved'] } for action in processlist: librarySync.triage_items(action, processlist[action]) elif message_type == "GeneralCommand": self._general_commands(data) elif message_type == "ServerRestarting": self._server_restarting() elif message_type == "UserConfigurationUpdated": # Update user data set in userclient userclient.UserClient().get_user(data) self.library_sync.refresh_views = True elif message_type == "ServerShuttingDown": # Server went offline window('emby_online', value="false") @classmethod def _play_(cls, data): item_ids = data['ItemIds'] command = data['PlayCommand'] playlist_ = playlist.Playlist() if command == "PlayNow": startat = data.get('StartPositionTicks', 0) playlist_.playAll(item_ids, startat) dialog(type_="notification", heading=lang(29999), message="%s %s" % (len(item_ids), lang(33004)), icon="{emby}", sound=False) elif command == "PlayNext": newplaylist = playlist_.modifyPlaylist(item_ids) dialog(type_="notification", heading=lang(29999), message="%s %s" % (len(item_ids), lang(33005)), icon="{emby}", sound=False) player = xbmc.Player() if not player.isPlaying(): # Only start the playlist if nothing is playing player.play(newplaylist) @classmethod def _playstate_(cls, data): 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") @classmethod def _general_commands(cls, data): 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(type_="notification", heading=header, message=text, icon="{emby}", 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) @classmethod def _server_restarting(cls): if settings('supressRestartMsg') == "true": dialog(type_="notification", heading=lang(29999), message=lang(33006), icon="{emby}") def on_close(self, ws): log.debug("closed") def on_open(self, ws): self.doutils.postCapabilities(self.device_id) def on_error(self, ws, error): if "10061" in str(error): # Server is offline pass else: log.debug("Error: %s", error) def run(self): # websocket.enableTrace(True) user_id = window('emby_currUser') server = window('emby_server%s' % user_id) token = window('emby_accessToken%s' % user_id) # 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.device_id) 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._stop_websocket: break if self.monitor.waitForAbort(5): # Abort was requested, exit break log.warn("##===---- WebSocketClient Stopped ----===##") def stop_client(self): self._stop_websocket = True self._client.close() log.info("Stopping thread")