2015-03-23 15:54:16 +11:00
|
|
|
#################################################################################################
|
|
|
|
# WebSocket Client thread
|
|
|
|
#################################################################################################
|
|
|
|
|
|
|
|
import xbmc
|
|
|
|
import xbmcgui
|
|
|
|
import xbmcaddon
|
|
|
|
|
|
|
|
import json
|
|
|
|
import threading
|
|
|
|
import urllib
|
|
|
|
import socket
|
|
|
|
import websocket
|
2015-04-11 22:56:07 +10:00
|
|
|
|
|
|
|
import KodiMonitor
|
2015-03-23 15:54:16 +11:00
|
|
|
from ClientInformation import ClientInformation
|
|
|
|
from DownloadUtils import DownloadUtils
|
|
|
|
from PlaybackUtils import PlaybackUtils
|
2015-03-26 16:15:45 +11:00
|
|
|
from LibrarySync import LibrarySync
|
2015-04-12 01:53:17 +10:00
|
|
|
from WriteKodiDB import WriteKodiDB
|
2015-04-06 03:29:09 +10:00
|
|
|
import Utils as utils
|
2015-03-23 15:54:16 +11:00
|
|
|
|
2015-04-19 03:10:06 +10:00
|
|
|
pendingUserDataList = []
|
|
|
|
pendingItemsToRemove = []
|
|
|
|
pendingItemsToUpdate = []
|
2015-03-23 15:54:16 +11:00
|
|
|
_MODE_BASICPLAY=12
|
|
|
|
|
|
|
|
class WebSocketThread(threading.Thread):
|
|
|
|
|
|
|
|
logLevel = 0
|
|
|
|
client = None
|
|
|
|
keepRunning = True
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
2015-04-11 22:56:07 +10:00
|
|
|
|
|
|
|
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
|
2015-03-26 04:37:21 +11:00
|
|
|
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
2015-03-23 15:54:16 +11:00
|
|
|
level = addonSettings.getSetting('logLevel')
|
|
|
|
self.logLevel = 0
|
|
|
|
if(level != None):
|
|
|
|
self.logLevel = int(level)
|
|
|
|
|
2015-03-26 04:37:21 +11:00
|
|
|
xbmc.log("emby WebSocketThread -> Log Level:" + str(self.logLevel))
|
2015-03-23 15:54:16 +11:00
|
|
|
|
|
|
|
threading.Thread.__init__(self, *args)
|
|
|
|
|
|
|
|
def logMsg(self, msg, level = 1):
|
|
|
|
if(self.logLevel >= level):
|
|
|
|
try:
|
2015-03-26 04:37:21 +11:00
|
|
|
xbmc.log("emby WebSocketThread -> " + str(msg))
|
2015-03-23 15:54:16 +11:00
|
|
|
except UnicodeEncodeError:
|
|
|
|
try:
|
2015-03-26 04:37:21 +11:00
|
|
|
xbmc.log("emby WebSocketThread -> " + str(msg.encode('utf-8')))
|
2015-03-23 15:54:16 +11:00
|
|
|
except: pass
|
|
|
|
|
|
|
|
'''
|
|
|
|
def playbackStarted(self, itemId):
|
|
|
|
if(self.client != None):
|
|
|
|
try:
|
|
|
|
self.logMsg("Sending Playback Started")
|
|
|
|
messageData = {}
|
|
|
|
messageData["MessageType"] = "PlaybackStart"
|
|
|
|
messageData["Data"] = itemId + "|true|audio,video"
|
|
|
|
messageString = json.dumps(messageData)
|
|
|
|
self.logMsg("Message Data : " + messageString)
|
|
|
|
self.client.send(messageString)
|
|
|
|
except Exception, e:
|
|
|
|
self.logMsg("Exception : " + str(e), level=0)
|
|
|
|
else:
|
|
|
|
self.logMsg("Sending Playback Started NO Object ERROR")
|
|
|
|
|
|
|
|
def playbackStopped(self, itemId, ticks):
|
|
|
|
if(self.client != None):
|
|
|
|
try:
|
|
|
|
self.logMsg("Sending Playback Stopped")
|
|
|
|
messageData = {}
|
|
|
|
messageData["MessageType"] = "PlaybackStopped"
|
|
|
|
messageData["Data"] = itemId + "|" + str(ticks)
|
|
|
|
messageString = json.dumps(messageData)
|
|
|
|
self.client.send(messageString)
|
|
|
|
except Exception, e:
|
|
|
|
self.logMsg("Exception : " + str(e), level=0)
|
|
|
|
else:
|
|
|
|
self.logMsg("Sending Playback Stopped NO Object ERROR")
|
|
|
|
'''
|
|
|
|
|
|
|
|
'''
|
|
|
|
def sendProgressUpdate(self, itemId, ticks):
|
|
|
|
if(self.client != None):
|
|
|
|
try:
|
|
|
|
self.logMsg("Sending Progress Update")
|
|
|
|
messageData = {}
|
|
|
|
messageData["MessageType"] = "PlaybackProgress"
|
|
|
|
messageData["Data"] = itemId + "|" + str(ticks) + "|false|false"
|
|
|
|
messageString = json.dumps(messageData)
|
|
|
|
self.logMsg("Message Data : " + messageString)
|
|
|
|
self.client.send(messageString)
|
|
|
|
except Exception, e:
|
|
|
|
self.logMsg("Exception : " + str(e), level=0)
|
|
|
|
else:
|
|
|
|
self.logMsg("Sending Progress Update NO Object ERROR")
|
|
|
|
'''
|
|
|
|
|
|
|
|
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 != None):
|
|
|
|
self.logMsg("Stopping Client")
|
|
|
|
self.keepRunning = False
|
|
|
|
self.client.keep_running = False
|
|
|
|
self.client.close()
|
|
|
|
self.logMsg("Stopping Client : KeepRunning set to False")
|
|
|
|
'''
|
|
|
|
try:
|
|
|
|
self.keepRunning = False
|
|
|
|
self.client.keep_running = False
|
|
|
|
self.logMsg("Stopping Client")
|
|
|
|
self.logMsg("Calling Ping")
|
|
|
|
self.client.sock.ping()
|
|
|
|
|
|
|
|
self.logMsg("Calling Socket Shutdown()")
|
|
|
|
self.client.sock.sock.shutdown(socket.SHUT_RDWR)
|
|
|
|
self.logMsg("Calling Socket Close()")
|
|
|
|
self.client.sock.sock.close()
|
|
|
|
self.logMsg("Stopping Client Done")
|
|
|
|
self.logMsg("Calling Ping")
|
|
|
|
self.client.sock.ping()
|
|
|
|
|
|
|
|
except Exception, e:
|
|
|
|
self.logMsg("Exception : " + str(e), level=0)
|
|
|
|
'''
|
|
|
|
else:
|
|
|
|
self.logMsg("Stopping Client NO Object ERROR")
|
|
|
|
|
|
|
|
def on_message(self, ws, message):
|
2015-04-19 03:10:06 +10:00
|
|
|
global pendingUserDataList
|
|
|
|
global pendingItemsToRemove
|
|
|
|
global pendingItemsToUpdate
|
2015-03-26 16:15:45 +11:00
|
|
|
self.logMsg("Message : " + str(message), 0)
|
2015-03-23 15:54:16 +11:00
|
|
|
result = json.loads(message)
|
|
|
|
|
|
|
|
messageType = result.get("MessageType")
|
|
|
|
data = result.get("Data")
|
|
|
|
|
|
|
|
if(messageType != None and messageType == "Play" and data != None):
|
|
|
|
itemIds = data.get("ItemIds")
|
|
|
|
playCommand = data.get("PlayCommand")
|
|
|
|
|
|
|
|
if(playCommand != None and playCommand == "PlayNow"):
|
|
|
|
|
|
|
|
xbmc.executebuiltin("Dialog.Close(all,true)")
|
|
|
|
startPositionTicks = data.get("StartPositionTicks")
|
|
|
|
PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks)
|
|
|
|
xbmc.executebuiltin("XBMC.Notification(Playlist: Added " + str(len(itemIds)) + " items to Playlist,)")
|
|
|
|
|
|
|
|
elif(playCommand != None and playCommand == "PlayNext"):
|
|
|
|
|
|
|
|
playlist = PlaybackUtils().AddToPlaylist(itemIds)
|
|
|
|
xbmc.executebuiltin("XBMC.Notification(Playlist: Added " + str(len(itemIds)) + " items to Playlist,)")
|
|
|
|
if(xbmc.Player().isPlaying() == False):
|
|
|
|
xbmc.Player().play(playlist)
|
|
|
|
|
|
|
|
elif(messageType != None and messageType == "Playstate"):
|
|
|
|
command = data.get("Command")
|
|
|
|
if(command != None and command == "Stop"):
|
|
|
|
self.logMsg("Playback Stopped")
|
|
|
|
xbmc.executebuiltin('xbmc.activatewindow(10000)')
|
|
|
|
xbmc.Player().stop()
|
|
|
|
elif(command != None and command == "Pause"):
|
|
|
|
self.logMsg("Playback Paused")
|
|
|
|
xbmc.Player().pause()
|
|
|
|
elif(command != None and command == "Unpause"):
|
|
|
|
self.logMsg("Playback UnPaused")
|
|
|
|
xbmc.Player().pause()
|
|
|
|
elif(command != None and command == "NextTrack"):
|
|
|
|
self.logMsg("Playback NextTrack")
|
|
|
|
xbmc.Player().playnext()
|
|
|
|
elif(command != None and command == "PreviousTrack"):
|
|
|
|
self.logMsg("Playback PreviousTrack")
|
|
|
|
xbmc.Player().playprevious()
|
|
|
|
elif(command != None and command == "Seek"):
|
|
|
|
seekPositionTicks = data.get("SeekPositionTicks")
|
|
|
|
self.logMsg("Playback Seek : " + str(seekPositionTicks))
|
|
|
|
seekTime = (seekPositionTicks / 1000) / 10000
|
|
|
|
xbmc.Player().seekTime(seekTime)
|
2015-03-26 16:15:45 +11:00
|
|
|
|
|
|
|
elif(messageType != None and messageType == "UserDataChanged"):
|
|
|
|
# for now just do a full playcount sync
|
2015-04-03 10:41:39 +11:00
|
|
|
WINDOW = xbmcgui.Window( 10000 )
|
|
|
|
self.logMsg("Message : Doing UserDataChanged", 0)
|
|
|
|
userDataList = data.get("UserDataList")
|
|
|
|
self.logMsg("Message : Doing UserDataChanged : UserDataList : " + str(userDataList), 0)
|
|
|
|
if(userDataList != None):
|
2015-04-19 03:10:06 +10:00
|
|
|
if xbmc.Player().isPlaying():
|
|
|
|
pendingUserDataList += userDataList
|
|
|
|
else:
|
|
|
|
self.user_data_update(userDataList)
|
2015-03-23 15:54:16 +11:00
|
|
|
|
2015-04-03 13:12:09 +11:00
|
|
|
elif(messageType != None and messageType == "LibraryChanged"):
|
|
|
|
foldersAddedTo = data.get("FoldersAddedTo")
|
|
|
|
foldersRemovedFrom = data.get("FoldersRemovedFrom")
|
|
|
|
|
|
|
|
# doing items removed
|
|
|
|
itemsRemoved = data.get("ItemsRemoved")
|
|
|
|
itemsAdded = data.get("ItemsAdded")
|
|
|
|
itemsUpdated = data.get("ItemsUpdated")
|
|
|
|
itemsToUpdate = itemsAdded + itemsUpdated
|
2015-04-19 03:10:06 +10:00
|
|
|
self.logMsg("Message : WebSocket LibraryChanged : Items Added : " + str(itemsAdded), 0)
|
|
|
|
self.logMsg("Message : WebSocket LibraryChanged : Items Updated : " + str(itemsUpdated), 0)
|
|
|
|
self.logMsg("Message : WebSocket LibraryChanged : Items Removed : " + str(itemsRemoved), 0)
|
|
|
|
|
|
|
|
if xbmc.Player().isPlaying():
|
|
|
|
pendingItemsToRemove += itemsRemoved
|
|
|
|
pendingItemsToUpdate += itemsToUpdate
|
|
|
|
else:
|
|
|
|
self.remove_items(itemsRemoved)
|
|
|
|
self.update_items(itemsToUpdate)
|
|
|
|
|
|
|
|
def remove_items(self, itemsRemoved):
|
|
|
|
for item in itemsRemoved:
|
|
|
|
self.logMsg("Message : Doing LibraryChanged : Items Removed : Calling deleteEpisodeFromKodiLibraryByMbId: " + item, 0)
|
|
|
|
WriteKodiDB().deleteEpisodeFromKodiLibraryByMbId(item)
|
|
|
|
self.logMsg("Message : Doing LibraryChanged : Items Removed : Calling deleteMovieFromKodiLibrary: " + item, 0)
|
|
|
|
WriteKodiDB().deleteMovieFromKodiLibrary(item)
|
|
|
|
self.logMsg("Message : Doing LibraryChanged : Items Removed : Calling deleteMusicVideoFromKodiLibrary: " + item, 0)
|
|
|
|
WriteKodiDB().deleteMusicVideoFromKodiLibrary(item)
|
|
|
|
|
|
|
|
def update_items(self, itemsToUpdate):
|
|
|
|
# doing adds and updates
|
|
|
|
if(len(itemsToUpdate) > 0):
|
|
|
|
self.logMsg("Message : Doing LibraryChanged : Processing Added and Updated : " + str(itemsToUpdate), 0)
|
|
|
|
connection = utils.KodiSQL()
|
|
|
|
cursor = connection.cursor()
|
|
|
|
LibrarySync().MoviesSync(connection, cursor, fullsync = False, installFirstRun = False, itemList = itemsToUpdate)
|
|
|
|
LibrarySync().TvShowsSync(connection, cursor, fullsync = False, installFirstRun = False, itemList = itemsToUpdate)
|
|
|
|
cursor.close()
|
|
|
|
|
2015-04-22 12:20:02 +10:00
|
|
|
def user_data_update(self, userDataList):
|
2015-04-19 03:10:06 +10:00
|
|
|
|
|
|
|
for userData in userDataList:
|
|
|
|
self.logMsg("Message : Doing UserDataChanged : UserData : " + str(userData), 0)
|
|
|
|
itemId = userData.get("ItemId")
|
|
|
|
if(itemId != None):
|
|
|
|
self.logMsg("Message : Doing UserDataChanged : calling updatePlayCount with ID : " + str(itemId), 0)
|
|
|
|
LibrarySync().updatePlayCount(itemId)
|
|
|
|
|
2015-03-23 15:54:16 +11:00
|
|
|
def on_error(self, ws, error):
|
|
|
|
self.logMsg("Error : " + str(error))
|
|
|
|
#raise
|
|
|
|
|
|
|
|
def on_close(self, ws):
|
|
|
|
self.logMsg("Closed")
|
|
|
|
|
|
|
|
def on_open(self, ws):
|
|
|
|
|
|
|
|
clientInfo = ClientInformation()
|
|
|
|
machineId = clientInfo.getMachineId()
|
|
|
|
version = clientInfo.getVersion()
|
|
|
|
messageData = {}
|
|
|
|
messageData["MessageType"] = "Identity"
|
|
|
|
|
2015-03-26 04:37:21 +11:00
|
|
|
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
2015-03-23 15:54:16 +11:00
|
|
|
deviceName = addonSettings.getSetting('deviceName')
|
|
|
|
deviceName = deviceName.replace("\"", "_")
|
|
|
|
|
|
|
|
messageData["Data"] = "Kodi|" + machineId + "|" + version + "|" + deviceName
|
|
|
|
messageString = json.dumps(messageData)
|
|
|
|
self.logMsg("Opened : " + str(messageString))
|
|
|
|
ws.send(messageString)
|
2015-04-22 12:20:02 +10:00
|
|
|
|
2015-03-23 15:54:16 +11:00
|
|
|
# Set Capabilities
|
|
|
|
xbmc.log("postcapabilities_called")
|
|
|
|
downloadUtils = DownloadUtils()
|
2015-04-22 12:20:02 +10:00
|
|
|
downloadUtils.postcapabilities()
|
2015-03-23 15:54:16 +11:00
|
|
|
|
|
|
|
|
|
|
|
def getWebSocketPort(self, host, port):
|
|
|
|
|
|
|
|
userUrl = "http://" + host + ":" + port + "/mediabrowser/System/Info?format=json"
|
|
|
|
|
|
|
|
downloadUtils = DownloadUtils()
|
2015-03-25 23:48:29 +11:00
|
|
|
jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 )
|
2015-03-23 15:54:16 +11:00
|
|
|
if(jsonData == ""):
|
|
|
|
return -1
|
|
|
|
|
|
|
|
result = json.loads(jsonData)
|
|
|
|
|
|
|
|
wsPort = result.get("WebSocketPortNumber")
|
|
|
|
if(wsPort != None):
|
|
|
|
return wsPort
|
|
|
|
else:
|
|
|
|
return -1
|
|
|
|
|
|
|
|
def run(self):
|
2015-03-26 04:37:21 +11:00
|
|
|
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
2015-04-14 04:56:36 +10:00
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
|
|
username = WINDOW.getProperty('currUser')
|
|
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
|
|
host = WINDOW.getProperty('server_%s' % username)
|
2015-03-23 15:54:16 +11:00
|
|
|
|
|
|
|
if(self.logLevel >= 1):
|
|
|
|
websocket.enableTrace(True)
|
|
|
|
'''
|
|
|
|
wsPort = self.getWebSocketPort(mb3Host, mb3Port);
|
|
|
|
self.logMsg("WebSocketPortNumber = " + str(wsPort))
|
|
|
|
if(wsPort == -1):
|
|
|
|
self.logMsg("Could not retrieve WebSocket port, can not run WebScoket Client")
|
|
|
|
return
|
|
|
|
'''
|
2015-04-14 04:56:36 +10:00
|
|
|
if "https" in server:
|
|
|
|
webSocketUrl = "wss://%s/mediabrowser" % host
|
|
|
|
else:
|
|
|
|
webSocketUrl = "ws://%s/mediabrowser" % host
|
2015-03-23 15:54:16 +11:00
|
|
|
# Make a call to /System/Info. WebSocketPortNumber is the port hosting the web socket.
|
2015-04-14 04:56:36 +10:00
|
|
|
#webSocketUrl = "ws://" + host + "/mediabrowser"
|
2015-03-23 15:54:16 +11:00
|
|
|
self.logMsg("WebSocket URL : " + 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
|
|
|
|
|
2015-04-11 22:56:07 +10:00
|
|
|
while not self.KodiMonitor.abortRequested():
|
2015-03-23 15:54:16 +11:00
|
|
|
self.logMsg("Client Starting")
|
|
|
|
self.client.run_forever()
|
|
|
|
if(self.keepRunning):
|
|
|
|
self.logMsg("Client Needs To Restart")
|
2015-04-12 13:01:44 +10:00
|
|
|
if self.KodiMonitor.waitForAbort(5):
|
2015-04-11 22:56:07 +10:00
|
|
|
break
|
2015-03-23 15:54:16 +11:00
|
|
|
self.logMsg("Thread Exited")
|
|
|
|
|
|
|
|
|
2015-04-19 03:10:06 +10:00
|
|
|
def processPendingActions(self):
|
|
|
|
global pendingUserDataList
|
|
|
|
global pendingItemsToRemove
|
|
|
|
global pendingItemsToUpdate
|
|
|
|
if pendingUserDataList != []:
|
|
|
|
self.user_data_update(pendingUserDataList)
|
|
|
|
pendingUserDataList = []
|
|
|
|
if pendingItemsToRemove != []:
|
|
|
|
self.remove_items(pendingItemsToRemove)
|
|
|
|
pendingItemsToRemove = []
|
|
|
|
if pendingItemsToUpdate != []:
|
|
|
|
self.update_items(pendingItemsToUpdate)
|
|
|
|
pendingItemsToUpdate = []
|
|
|
|
|