Attempt #1 at fixing playback report/resume
Cleaning up the code, using websocket to report playback progress.
This commit is contained in:
parent
ed899e9365
commit
2f3d609f53
5 changed files with 181 additions and 269 deletions
|
@ -32,16 +32,16 @@ class DownloadUtils():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
self.__dict__ = self._shared_state
|
||||||
self.className = self.__class__.__name__
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
self.className = self.__class__.__name__
|
||||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
||||||
|
|
||||||
def setUsername(self, username):
|
def setUsername(self, username):
|
||||||
# Reserved for UserClient only
|
# Reserved for UserClient only
|
||||||
self.username = username
|
self.username = username
|
||||||
self.logMsg("Set username: %s" % username, 1)
|
self.logMsg("Set username: %s" % username, 2)
|
||||||
|
|
||||||
def setUserId(self, userId):
|
def setUserId(self, userId):
|
||||||
# Reserved for UserClient only
|
# Reserved for UserClient only
|
||||||
|
@ -71,8 +71,9 @@ class DownloadUtils():
|
||||||
url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
|
url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
|
||||||
result = self.downloadUrl(url)
|
result = self.downloadUrl(url)
|
||||||
# sessionId result
|
# sessionId result
|
||||||
self.logMsg("Session result: %s" % result, 1)
|
self.logMsg("Session result: %s" % result, 2)
|
||||||
self.sessionId = result[0][u'Id']
|
self.sessionId = result[0][u'Id']
|
||||||
|
self.WINDOW.setProperty('sessionId%s' % self.username, self.sessionId)
|
||||||
|
|
||||||
# Settings for capabilities
|
# Settings for capabilities
|
||||||
playableMediaTypes = "Audio,Video"
|
playableMediaTypes = "Audio,Video"
|
||||||
|
@ -129,7 +130,7 @@ class DownloadUtils():
|
||||||
if not authenticate:
|
if not authenticate:
|
||||||
# If user is not authenticated
|
# If user is not authenticated
|
||||||
auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
|
auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
|
||||||
header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth}
|
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth}
|
||||||
|
|
||||||
self.logMsg("Header: %s" % header, 2)
|
self.logMsg("Header: %s" % header, 2)
|
||||||
return header
|
return header
|
||||||
|
@ -139,7 +140,7 @@ class DownloadUtils():
|
||||||
token = self.token
|
token = self.token
|
||||||
# Attached to the requests session
|
# Attached to the requests session
|
||||||
auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
|
auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
|
||||||
header = {"Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": auth, "X-MediaBrowser-Token": token}
|
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth, 'X-MediaBrowser-Token': token}
|
||||||
|
|
||||||
self.logMsg("Header: %s" % header, 2)
|
self.logMsg("Header: %s" % header, 2)
|
||||||
return header
|
return header
|
||||||
|
@ -159,16 +160,16 @@ class DownloadUtils():
|
||||||
# Replace for the real values and append api_key
|
# Replace for the real values and append api_key
|
||||||
url = url.replace("{server}", self.server, 1)
|
url = url.replace("{server}", self.server, 1)
|
||||||
url = url.replace("{UserId}", self.userId, 1)
|
url = url.replace("{UserId}", self.userId, 1)
|
||||||
url = "%s&api_key=%s" % (url, self.token)
|
#url = "%s&api_key=%s" % (url, self.token)
|
||||||
|
|
||||||
self.logMsg("URL: %s" % url, 2)
|
self.logMsg("URL: %s" % url, 2)
|
||||||
# Prepare request
|
# Prepare request
|
||||||
if type == "GET":
|
if type == "GET":
|
||||||
r = s.get(url, params=postBody, timeout=timeout)
|
r = s.get(url, json=postBody, timeout=timeout)
|
||||||
elif type == "POST":
|
elif type == "POST":
|
||||||
r = s.post(url, params=postBody, timeout=timeout)
|
r = s.post(url, json=postBody, timeout=timeout)
|
||||||
elif type == "DELETE":
|
elif type == "DELETE":
|
||||||
r = s.delete(url, params=postBody, timeout=timeout)
|
r = s.delete(url, json=postBody, timeout=timeout)
|
||||||
|
|
||||||
# If user is not authenticated
|
# If user is not authenticated
|
||||||
elif not authenticate:
|
elif not authenticate:
|
||||||
|
@ -185,9 +186,9 @@ class DownloadUtils():
|
||||||
|
|
||||||
# Prepare request
|
# Prepare request
|
||||||
if type == "GET":
|
if type == "GET":
|
||||||
r = requests.get(url, params=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
r = requests.get(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
||||||
elif type == "POST":
|
elif type == "POST":
|
||||||
r = requests.post(url, params=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
r = requests.post(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
|
||||||
|
|
||||||
# Process the response
|
# Process the response
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,8 +6,10 @@ import os
|
||||||
import threading
|
import threading
|
||||||
import json
|
import json
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import KodiMonitor
|
import KodiMonitor
|
||||||
import Utils as utils
|
import Utils as utils
|
||||||
|
|
||||||
from DownloadUtils import DownloadUtils
|
from DownloadUtils import DownloadUtils
|
||||||
from WebSocketClient import WebSocketThread
|
from WebSocketClient import WebSocketThread
|
||||||
from PlayUtils import PlayUtils
|
from PlayUtils import PlayUtils
|
||||||
|
@ -21,36 +23,34 @@ librarySync = LibrarySync()
|
||||||
# service class for playback monitoring
|
# service class for playback monitoring
|
||||||
class Player( xbmc.Player ):
|
class Player( xbmc.Player ):
|
||||||
|
|
||||||
|
# Borg - multiple instances, shared state
|
||||||
|
_shared_state = {}
|
||||||
|
|
||||||
|
xbmcplayer = xbmc.Player()
|
||||||
|
doUtils = DownloadUtils()
|
||||||
|
clientInfo = ClientInformation()
|
||||||
|
ws = WebSocketThread()
|
||||||
|
|
||||||
|
addonName = clientInfo.getAddonName()
|
||||||
|
addonId = clientInfo.getAddonId()
|
||||||
|
addon = xbmcaddon.Addon(id=addonId)
|
||||||
|
|
||||||
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
|
||||||
logLevel = 0
|
logLevel = 0
|
||||||
played_information = {}
|
played_information = {}
|
||||||
downloadUtils = None
|
|
||||||
settings = None
|
settings = None
|
||||||
playStats = {}
|
playStats = {}
|
||||||
|
|
||||||
def __init__( self, *args ):
|
def __init__( self, *args ):
|
||||||
|
|
||||||
self.settings = xbmcaddon.Addon(id='plugin.video.emby')
|
self.__dict__ = self._shared_state
|
||||||
self.downloadUtils = DownloadUtils()
|
self.logMsg("Starting playback monitor service", 1)
|
||||||
try:
|
|
||||||
self.logLevel = int(self.settings.getSetting('logLevel'))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.printDebug("emby Service -> starting playback monitor service",1)
|
|
||||||
self.played_information = {}
|
|
||||||
pass
|
|
||||||
|
|
||||||
def printDebug(self, msg, level = 1):
|
def logMsg(self, msg, lvl=1):
|
||||||
if(self.logLevel >= level):
|
|
||||||
if(self.logLevel == 2):
|
self.className = self.__class__.__name__
|
||||||
try:
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
||||||
xbmc.log("emby " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
xbmc.log("emby " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
xbmc.log("emby " + str(level) + " -> " + str(msg))
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
xbmc.log("emby " + str(level) + " -> " + str(msg.encode('utf-8')))
|
|
||||||
|
|
||||||
def hasData(self, data):
|
def hasData(self, data):
|
||||||
if(data == None or len(data) == 0 or data == "None"):
|
if(data == None or len(data) == 0 or data == "None"):
|
||||||
|
@ -60,19 +60,19 @@ class Player( xbmc.Player ):
|
||||||
|
|
||||||
def stopAll(self):
|
def stopAll(self):
|
||||||
|
|
||||||
WebSocketThread().processPendingActions()
|
self.ws.processPendingActions()
|
||||||
if(len(self.played_information) == 0):
|
if(len(self.played_information) == 0):
|
||||||
return
|
return
|
||||||
|
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
||||||
self.printDebug("emby Service -> played_information : " + str(self.played_information))
|
self.logMsg("emby Service -> played_information : " + str(self.played_information))
|
||||||
|
|
||||||
for item_url in self.played_information:
|
for item_url in self.played_information:
|
||||||
data = self.played_information.get(item_url)
|
data = self.played_information.get(item_url)
|
||||||
|
|
||||||
if(data != None):
|
if (data is not None):
|
||||||
self.printDebug("emby Service -> item_url : " + item_url)
|
self.logMsg("emby Service -> item_url : " + item_url)
|
||||||
self.printDebug("emby Service -> item_data : " + str(data))
|
self.logMsg("emby Service -> item_data : " + str(data))
|
||||||
|
|
||||||
runtime = data.get("runtime")
|
runtime = data.get("runtime")
|
||||||
currentPosition = data.get("currentPosition")
|
currentPosition = data.get("currentPosition")
|
||||||
|
@ -83,11 +83,11 @@ class Player( xbmc.Player ):
|
||||||
|
|
||||||
if(currentPosition != None and self.hasData(runtime)):
|
if(currentPosition != None and self.hasData(runtime)):
|
||||||
runtimeTicks = int(runtime)
|
runtimeTicks = int(runtime)
|
||||||
self.printDebug("emby Service -> runtimeticks:" + str(runtimeTicks))
|
self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks))
|
||||||
percentComplete = (currentPosition * 10000000) / runtimeTicks
|
percentComplete = (currentPosition * 10000000) / runtimeTicks
|
||||||
markPlayedAt = float(90) / 100
|
markPlayedAt = float(90) / 100
|
||||||
|
|
||||||
self.printDebug("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
|
self.logMsg("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
|
||||||
self.stopPlayback(data)
|
self.stopPlayback(data)
|
||||||
|
|
||||||
if(refresh_id != None):
|
if(refresh_id != None):
|
||||||
|
@ -96,125 +96,116 @@ class Player( xbmc.Player ):
|
||||||
|
|
||||||
|
|
||||||
self.played_information.clear()
|
self.played_information.clear()
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
username = WINDOW.getProperty('currUser')
|
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
|
||||||
|
|
||||||
# stop transcoding - todo check we are actually transcoding?
|
# stop transcoding - todo check we are actually transcoding?
|
||||||
clientInfo = ClientInformation()
|
clientInfo = ClientInformation()
|
||||||
txt_mac = clientInfo.getMachineId()
|
txt_mac = clientInfo.getMachineId()
|
||||||
url = "%s/mediabrowser/Videos/ActiveEncodings" % server
|
url = "{server}/mediabrowser/Videos/ActiveEncodings"
|
||||||
url = url + '?DeviceId=' + txt_mac
|
url = url + '?DeviceId=' + txt_mac
|
||||||
self.downloadUtils.downloadUrl(url, type="DELETE")
|
self.doUtils.downloadUrl(url, type="DELETE")
|
||||||
|
|
||||||
def stopPlayback(self, data):
|
def stopPlayback(self, data):
|
||||||
self.printDebug("stopPlayback called")
|
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
self.logMsg("stopPlayback called", 2)
|
||||||
|
|
||||||
item_id = data.get("item_id")
|
item_id = data.get("item_id")
|
||||||
audioindex = data.get("AudioStreamIndex")
|
audioindex = data.get("AudioStreamIndex")
|
||||||
subtitleindex = data.get("SubtitleStreamIndex")
|
subtitleindex = data.get("SubtitleStreamIndex")
|
||||||
playMethod = data.get("playmethod")
|
playMethod = data.get("playmethod")
|
||||||
currentPosition = data.get("currentPosition")
|
currentPosition = data.get("currentPosition")
|
||||||
positionTicks = str(int(currentPosition * 10000000))
|
positionTicks = int(currentPosition * 10000000)
|
||||||
|
|
||||||
|
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
|
||||||
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
postdata = {
|
||||||
username = WINDOW.getProperty('currUser')
|
'QueueableMediaTypes': "Video",
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
'CanSeek': True,
|
||||||
|
'ItemId': item_id,
|
||||||
|
'MediaSourceId': item_id,
|
||||||
|
'PlayMethod': playMethod,
|
||||||
|
'PositionTicks': positionTicks
|
||||||
|
}
|
||||||
|
|
||||||
url = "%s/mediabrowser/Sessions/Playing/Stopped" % server
|
if audioindex:
|
||||||
|
postdata['AudioStreamIndex'] = audioindex
|
||||||
url = url + "?itemId=" + item_id
|
|
||||||
|
|
||||||
url = url + "&canSeek=true"
|
if subtitleindex:
|
||||||
url = url + "&PlayMethod=" + playMethod
|
postdata['SubtitleStreamIndex'] = subtitleindex
|
||||||
url = url + "&QueueableMediaTypes=Video"
|
|
||||||
url = url + "&MediaSourceId=" + item_id
|
|
||||||
url = url + "&PositionTicks=" + positionTicks
|
|
||||||
if(audioindex != None and audioindex!=""):
|
|
||||||
url = url + "&AudioStreamIndex=" + audioindex
|
|
||||||
|
|
||||||
if(subtitleindex != None and subtitleindex!=""):
|
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
|
||||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
|
||||||
|
|
||||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
|
||||||
|
|
||||||
|
|
||||||
def reportPlayback(self):
|
def reportPlayback(self):
|
||||||
self.printDebug("reportPlayback Called",2)
|
|
||||||
|
|
||||||
currentFile = xbmc.Player().getPlayingFile()
|
|
||||||
|
|
||||||
#TODO need to change this to use the one in the data map
|
|
||||||
playTime = xbmc.Player().getTime()
|
|
||||||
|
|
||||||
|
self.logMsg("reportPlayback Called", 2)
|
||||||
|
xbmcplayer = self.xbmcplayer
|
||||||
|
|
||||||
|
currentFile = xbmcplayer.getPlayingFile()
|
||||||
data = self.played_information.get(currentFile)
|
data = self.played_information.get(currentFile)
|
||||||
|
|
||||||
# only report playback if emby has initiated the playback (item_id has value)
|
# only report playback if emby has initiated the playback (item_id has value)
|
||||||
if(data != None and data.get("item_id") != None):
|
if (data is not None) and (data.get("item_id") is not None):
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
|
# Get playback information
|
||||||
item_id = data.get("item_id")
|
item_id = data.get("item_id")
|
||||||
audioindex = data.get("AudioStreamIndex")
|
audioindex = data.get("AudioStreamIndex")
|
||||||
subtitleindex = data.get("SubtitleStreamIndex")
|
subtitleindex = data.get("SubtitleStreamIndex")
|
||||||
|
playTime = data.get("currentPosition")
|
||||||
playMethod = data.get("playmethod")
|
playMethod = data.get("playmethod")
|
||||||
paused = data.get("paused")
|
paused = data.get("paused")
|
||||||
|
|
||||||
|
if paused is None:
|
||||||
|
paused = False
|
||||||
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
#url = "{server}/mediabrowser/Sessions/Playing/Progress"
|
||||||
username = WINDOW.getProperty('currUser')
|
postdata = {
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
'QueueableMediaTypes': "Video",
|
||||||
|
'CanSeek': True,
|
||||||
url = "%s/mediabrowser/Sessions/Playing/Progress" % server
|
'ItemId': item_id,
|
||||||
|
'MediaSourceId': item_id,
|
||||||
url = url + "?itemId=" + item_id
|
'IsPaused': paused,
|
||||||
|
'PlayMethod': playMethod
|
||||||
|
}
|
||||||
|
|
||||||
url = url + "&canSeek=true"
|
if playTime:
|
||||||
url = url + "&PlayMethod=" + playMethod
|
postdata['PositionTicks'] = int(playTime * 10000000)
|
||||||
url = url + "&QueueableMediaTypes=Video"
|
|
||||||
url = url + "&MediaSourceId=" + item_id
|
if audioindex:
|
||||||
|
postdata['AudioStreamIndex'] = audioindex
|
||||||
url = url + "&PositionTicks=" + str(int(playTime * 10000000))
|
|
||||||
|
if subtitleindex:
|
||||||
if(audioindex != None and audioindex!=""):
|
postdata['SubtitleStreamIndex'] = subtitleindex
|
||||||
url = url + "&AudioStreamIndex=" + audioindex
|
|
||||||
|
postdata = json.dumps(postdata)
|
||||||
if(subtitleindex != None and subtitleindex!=""):
|
self.logMsg("Report: %s" % postdata)
|
||||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
self.ws.sendProgressUpdate(postdata)
|
||||||
|
|
||||||
if(paused == None):
|
|
||||||
paused = "false"
|
|
||||||
url = url + "&IsPaused=" + paused
|
|
||||||
|
|
||||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
|
||||||
|
|
||||||
def onPlayBackPaused( self ):
|
def onPlayBackPaused( self ):
|
||||||
currentFile = xbmc.Player().getPlayingFile()
|
currentFile = xbmc.Player().getPlayingFile()
|
||||||
self.printDebug("PLAYBACK_PAUSED : " + currentFile,2)
|
self.logMsg("PLAYBACK_PAUSED : " + currentFile,2)
|
||||||
if(self.played_information.get(currentFile) != None):
|
if(self.played_information.get(currentFile) != None):
|
||||||
self.played_information[currentFile]["paused"] = "true"
|
self.played_information[currentFile]["paused"] = "true"
|
||||||
self.reportPlayback()
|
self.reportPlayback()
|
||||||
|
|
||||||
def onPlayBackResumed( self ):
|
def onPlayBackResumed( self ):
|
||||||
currentFile = xbmc.Player().getPlayingFile()
|
currentFile = xbmc.Player().getPlayingFile()
|
||||||
self.printDebug("PLAYBACK_RESUMED : " + currentFile,2)
|
self.logMsg("PLAYBACK_RESUMED : " + currentFile,2)
|
||||||
if(self.played_information.get(currentFile) != None):
|
if(self.played_information.get(currentFile) != None):
|
||||||
self.played_information[currentFile]["paused"] = "false"
|
self.played_information[currentFile]["paused"] = "false"
|
||||||
self.reportPlayback()
|
self.reportPlayback()
|
||||||
|
|
||||||
def onPlayBackSeek( self, time, seekOffset ):
|
def onPlayBackSeek( self, time, seekOffset ):
|
||||||
self.printDebug("PLAYBACK_SEEK",2)
|
self.logMsg("PLAYBACK_SEEK",2)
|
||||||
self.reportPlayback()
|
self.reportPlayback()
|
||||||
|
|
||||||
def onPlayBackStarted( self ):
|
def onPlayBackStarted( self ):
|
||||||
# Will be called when xbmc starts playing a file
|
# Will be called when xbmc starts playing a file
|
||||||
WINDOW = xbmcgui.Window( 10000 )
|
WINDOW = self.WINDOW
|
||||||
|
xbmcplayer = self.xbmcplayer
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
xbmcplayer = xbmc.Player()
|
|
||||||
|
|
||||||
if xbmcplayer.isPlaying():
|
if xbmcplayer.isPlaying():
|
||||||
currentFile = xbmcplayer.getPlayingFile()
|
currentFile = xbmcplayer.getPlayingFile()
|
||||||
self.printDebug("emby Service -> onPlayBackStarted : " + currentFile, 0)
|
self.logMsg("onPlayBackStarted: %s" % currentFile, 0)
|
||||||
|
|
||||||
# we may need to wait until the info is available
|
# we may need to wait until the info is available
|
||||||
item_id = WINDOW.getProperty(currentFile + "item_id")
|
item_id = WINDOW.getProperty(currentFile + "item_id")
|
||||||
|
@ -236,33 +227,35 @@ class Player( xbmc.Player ):
|
||||||
playMethod = WINDOW.getProperty(currentFile + "playmethod")
|
playMethod = WINDOW.getProperty(currentFile + "playmethod")
|
||||||
itemType = WINDOW.getProperty(currentFile + "type")
|
itemType = WINDOW.getProperty(currentFile + "type")
|
||||||
seekTime = WINDOW.getProperty(currentFile + "seektime")
|
seekTime = WINDOW.getProperty(currentFile + "seektime")
|
||||||
|
|
||||||
|
username = WINDOW.getProperty('currUser')
|
||||||
|
sessionId = WINDOW.getProperty('sessionId%s' % username)
|
||||||
|
|
||||||
if seekTime != "":
|
if seekTime != "":
|
||||||
PlaybackUtils().seekToPosition(int(seekTime))
|
PlaybackUtils().seekToPosition(int(seekTime))
|
||||||
|
|
||||||
if(item_id == None or len(item_id) == 0):
|
if (not item_id) or (len(item_id) == 0):
|
||||||
self.printDebug("emby Service -> onPlayBackStarted : No info for current playing file", 0)
|
self.logMsg("onPlayBackStarted: No info for current playing file", 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
username = WINDOW.getProperty('currUser')
|
url = "{server}/mediabrowser/Sessions/Playing"
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
postdata = {
|
||||||
|
'QueueableMediaTypes': "Video",
|
||||||
|
'CanSeek': True,
|
||||||
|
'ItemId': item_id,
|
||||||
|
'MediaSourceId': item_id,
|
||||||
|
'PlayMethod': playMethod
|
||||||
|
}
|
||||||
|
|
||||||
url = "%s/mediabrowser/Sessions/Playing" % server
|
if audioindex:
|
||||||
|
postdata['AudioStreamIndex'] = audioindex
|
||||||
|
|
||||||
url = url + "?itemId=" + item_id
|
if subtitleindex:
|
||||||
|
postdata['SubtitleStreamIndex'] = subtitleindex
|
||||||
url = url + "&canSeek=true"
|
|
||||||
url = url + "&PlayMethod=" + playMethod
|
|
||||||
url = url + "&QueueableMediaTypes=Video"
|
|
||||||
url = url + "&MediaSourceId=" + item_id
|
|
||||||
|
|
||||||
if(audioindex != None and audioindex!=""):
|
self.logMsg("Sending POST play started.", 1)
|
||||||
url = url + "&AudioStreamIndex=" + audioindex
|
#self.logMsg("emby Service -> Sending Post Play Started : " + url, 0)
|
||||||
|
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
|
||||||
if(subtitleindex != None and subtitleindex!=""):
|
|
||||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
|
||||||
|
|
||||||
self.printDebug("emby Service -> Sending Post Play Started : " + url, 0)
|
|
||||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
|
||||||
|
|
||||||
# save data map for updates and position calls
|
# save data map for updates and position calls
|
||||||
data = {}
|
data = {}
|
||||||
|
@ -276,8 +269,8 @@ class Player( xbmc.Player ):
|
||||||
data["Type"] = itemType
|
data["Type"] = itemType
|
||||||
self.played_information[currentFile] = data
|
self.played_information[currentFile] = data
|
||||||
|
|
||||||
self.printDebug("emby Service -> ADDING_FILE : " + currentFile, 0)
|
self.logMsg("emby Service -> ADDING_FILE : " + currentFile, 0)
|
||||||
self.printDebug("emby Service -> ADDING_FILE : " + str(self.played_information), 0)
|
self.logMsg("emby Service -> ADDING_FILE : " + str(self.played_information), 0)
|
||||||
|
|
||||||
# log some playback stats
|
# log some playback stats
|
||||||
if(itemType != None):
|
if(itemType != None):
|
||||||
|
@ -302,7 +295,7 @@ class Player( xbmc.Player ):
|
||||||
|
|
||||||
def onPlayBackEnded( self ):
|
def onPlayBackEnded( self ):
|
||||||
# Will be called when xbmc stops playing a file
|
# Will be called when xbmc stops playing a file
|
||||||
self.printDebug("emby Service -> onPlayBackEnded")
|
self.logMsg("onPlayBackEnded", 0)
|
||||||
|
|
||||||
#workaround when strm files are launched through the addon - mark watched when finished playing
|
#workaround when strm files are launched through the addon - mark watched when finished playing
|
||||||
#TODO --> mark watched when 95% is played of the file
|
#TODO --> mark watched when 95% is played of the file
|
||||||
|
@ -311,12 +304,8 @@ class Player( xbmc.Player ):
|
||||||
try:
|
try:
|
||||||
id = WINDOW.getProperty("virtualstrm")
|
id = WINDOW.getProperty("virtualstrm")
|
||||||
type = WINDOW.getProperty("virtualstrmtype")
|
type = WINDOW.getProperty("virtualstrmtype")
|
||||||
addon = xbmcaddon.Addon(id='plugin.video.emby')
|
watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % id
|
||||||
username = WINDOW.getProperty('currUser')
|
self.doUtils.downloadUrl(watchedurl, postBody="", type="POST")
|
||||||
userid = WINDOW.getProperty('userId%s' % username)
|
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
|
||||||
watchedurl = "%s/mediabrowser/Users/%s/PlayedItems/%s" % (server, userid, id)
|
|
||||||
self.downloadUtils.downloadUrl(watchedurl, postBody="", type="POST")
|
|
||||||
librarySync.updatePlayCount(id)
|
librarySync.updatePlayCount(id)
|
||||||
except: pass
|
except: pass
|
||||||
WINDOW.clearProperty("virtualstrm")
|
WINDOW.clearProperty("virtualstrm")
|
||||||
|
@ -325,7 +314,7 @@ class Player( xbmc.Player ):
|
||||||
|
|
||||||
def onPlayBackStopped( self ):
|
def onPlayBackStopped( self ):
|
||||||
# Will be called when user stops xbmc playing a file
|
# Will be called when user stops xbmc playing a file
|
||||||
self.printDebug("emby Service -> onPlayBackStopped")
|
self.logMsg("onPlayBackStopped", 0)
|
||||||
self.stopAll()
|
self.stopAll()
|
||||||
|
|
||||||
|
|
||||||
|
@ -352,8 +341,8 @@ class Player( xbmc.Player ):
|
||||||
if userData!=None and userData["Played"]==True:
|
if userData!=None and userData["Played"]==True:
|
||||||
pDialog = xbmcgui.DialogProgress()
|
pDialog = xbmcgui.DialogProgress()
|
||||||
seasonId = MB3Episode["SeasonId"]
|
seasonId = MB3Episode["SeasonId"]
|
||||||
url = "%s/mediabrowser/Users/%s/Items?ParentId=%s&ImageTypeLimit=1&Limit=1&SortBy=SortName&SortOrder=Ascending&Filters=IsUnPlayed&IncludeItemTypes=Episode&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json" % (server, userid, seasonId)
|
url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&ImageTypeLimit=1&Limit=1&SortBy=SortName&SortOrder=Ascending&Filters=IsUnPlayed&IncludeItemTypes=Episode&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json" % seasonId
|
||||||
jsonData = self.downloadUtils.downloadUrl(url, suppress=False, popup=1 )
|
jsonData = self.doUtils.downloadUrl(url)
|
||||||
if(jsonData != ""):
|
if(jsonData != ""):
|
||||||
seasonData = json.loads(jsonData)
|
seasonData = json.loads(jsonData)
|
||||||
if seasonData.get("Items") != None:
|
if seasonData.get("Items") != None:
|
||||||
|
|
|
@ -30,6 +30,8 @@ language = addonSettings.getLocalizedString
|
||||||
|
|
||||||
def logMsg(title, msg, level = 1):
|
def logMsg(title, msg, level = 1):
|
||||||
logLevel = int(addonSettings.getSetting("logLevel"))
|
logLevel = int(addonSettings.getSetting("logLevel"))
|
||||||
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
WINDOW.setProperty('logLevel', str(logLevel))
|
||||||
if(logLevel >= level):
|
if(logLevel >= level):
|
||||||
if(logLevel == 2): # inspect.stack() is expensive
|
if(logLevel == 2): # inspect.stack() is expensive
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -13,12 +13,13 @@ import socket
|
||||||
import websocket
|
import websocket
|
||||||
|
|
||||||
import KodiMonitor
|
import KodiMonitor
|
||||||
|
import Utils as utils
|
||||||
|
|
||||||
from ClientInformation import ClientInformation
|
from ClientInformation import ClientInformation
|
||||||
from DownloadUtils import DownloadUtils
|
from DownloadUtils import DownloadUtils
|
||||||
from PlaybackUtils import PlaybackUtils
|
from PlaybackUtils import PlaybackUtils
|
||||||
from LibrarySync import LibrarySync
|
from LibrarySync import LibrarySync
|
||||||
from WriteKodiDB import WriteKodiDB
|
from WriteKodiDB import WriteKodiDB
|
||||||
import Utils as utils
|
|
||||||
|
|
||||||
pendingUserDataList = []
|
pendingUserDataList = []
|
||||||
pendingItemsToRemove = []
|
pendingItemsToRemove = []
|
||||||
|
@ -27,79 +28,39 @@ _MODE_BASICPLAY=12
|
||||||
|
|
||||||
class WebSocketThread(threading.Thread):
|
class WebSocketThread(threading.Thread):
|
||||||
|
|
||||||
logLevel = 0
|
_shared_state = {}
|
||||||
|
|
||||||
|
clientInfo = ClientInformation()
|
||||||
|
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
||||||
|
addonName = clientInfo.getAddonName()
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
keepRunning = True
|
keepRunning = True
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
|
|
||||||
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
|
self.__dict__ = self._shared_state
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
level = addonSettings.getSetting('logLevel')
|
|
||||||
self.logLevel = 0
|
|
||||||
if(level != None):
|
|
||||||
self.logLevel = int(level)
|
|
||||||
|
|
||||||
xbmc.log("emby WebSocketThread -> Log Level:" + str(self.logLevel))
|
|
||||||
|
|
||||||
threading.Thread.__init__(self, *args)
|
threading.Thread.__init__(self, *args)
|
||||||
|
|
||||||
def logMsg(self, msg, level = 1):
|
def logMsg(self, msg, lvl=1):
|
||||||
if(self.logLevel >= level):
|
|
||||||
try:
|
|
||||||
xbmc.log("emby WebSocketThread -> " + str(msg))
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
try:
|
|
||||||
xbmc.log("emby WebSocketThread -> " + str(msg.encode('utf-8')))
|
|
||||||
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):
|
self.className = self.__class__.__name__
|
||||||
if(self.client != None):
|
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
|
||||||
|
|
||||||
|
def sendProgressUpdate(self, data):
|
||||||
|
self.logMsg("sendProgressUpdate", 1)
|
||||||
|
if self.client:
|
||||||
try:
|
try:
|
||||||
self.logMsg("Sending Playback Stopped")
|
# Send progress update
|
||||||
messageData = {}
|
messageData = {
|
||||||
messageData["MessageType"] = "PlaybackStopped"
|
'MessageType': "ReportPlaybackProgress",
|
||||||
messageData["Data"] = itemId + "|" + str(ticks)
|
'Data': data
|
||||||
|
}
|
||||||
messageString = json.dumps(messageData)
|
messageString = json.dumps(messageData)
|
||||||
self.client.send(messageString)
|
self.client.send(messageString)
|
||||||
|
self.logMsg("Message data: %s" % messageString, 2)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.logMsg("Exception : " + str(e), level=0)
|
self.logMsg("Exception: %s" % e, 1)
|
||||||
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):
|
def stopClient(self):
|
||||||
# stopping the client is tricky, first set keep_running to false and then trigger one
|
# stopping the client is tricky, first set keep_running to false and then trigger one
|
||||||
|
@ -253,69 +214,30 @@ class WebSocketThread(threading.Thread):
|
||||||
self.logMsg("Closed")
|
self.logMsg("Closed")
|
||||||
|
|
||||||
def on_open(self, ws):
|
def on_open(self, ws):
|
||||||
|
pass
|
||||||
clientInfo = ClientInformation()
|
|
||||||
machineId = clientInfo.getMachineId()
|
|
||||||
version = clientInfo.getVersion()
|
|
||||||
messageData = {}
|
|
||||||
messageData["MessageType"] = "Identity"
|
|
||||||
|
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
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)
|
|
||||||
'''
|
|
||||||
# Set Capabilities
|
|
||||||
xbmc.log("postcapabilities_called")
|
|
||||||
downloadUtils = DownloadUtils()
|
|
||||||
downloadUtils.startSession()'''
|
|
||||||
|
|
||||||
|
|
||||||
def getWebSocketPort(self, host, port):
|
|
||||||
|
|
||||||
userUrl = "http://" + host + ":" + port + "/mediabrowser/System/Info?format=json"
|
|
||||||
|
|
||||||
downloadUtils = DownloadUtils()
|
|
||||||
jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 )
|
|
||||||
if(jsonData == ""):
|
|
||||||
return -1
|
|
||||||
|
|
||||||
result = json.loads(jsonData)
|
|
||||||
|
|
||||||
wsPort = result.get("WebSocketPortNumber")
|
|
||||||
if(wsPort != None):
|
|
||||||
return wsPort
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
WINDOW = xbmcgui.Window(10000)
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
logLevel = int(WINDOW.getProperty('logLevel'))
|
||||||
username = WINDOW.getProperty('currUser')
|
username = WINDOW.getProperty('currUser')
|
||||||
server = WINDOW.getProperty('server%s' % username)
|
server = WINDOW.getProperty('server%s' % username)
|
||||||
host = WINDOW.getProperty('server_%s' % username)
|
token = WINDOW.getProperty('accessToken%s' % username)
|
||||||
|
deviceId = ClientInformation().getMachineId()
|
||||||
if(self.logLevel >= 1):
|
|
||||||
|
if (logLevel == 2):
|
||||||
websocket.enableTrace(True)
|
websocket.enableTrace(True)
|
||||||
'''
|
|
||||||
wsPort = self.getWebSocketPort(mb3Host, mb3Port);
|
# Get the appropriate prefix for websocket
|
||||||
self.logMsg("WebSocketPortNumber = " + str(wsPort))
|
|
||||||
if(wsPort == -1):
|
|
||||||
self.logMsg("Could not retrieve WebSocket port, can not run WebScoket Client")
|
|
||||||
return
|
|
||||||
'''
|
|
||||||
if "https" in server:
|
if "https" in server:
|
||||||
webSocketUrl = "wss://%s/mediabrowser" % host
|
server = server.replace('https', 'wss')
|
||||||
else:
|
else:
|
||||||
webSocketUrl = "ws://%s/mediabrowser" % host
|
server = server.replace('http', 'ws')
|
||||||
# Make a call to /System/Info. WebSocketPortNumber is the port hosting the web socket.
|
|
||||||
#webSocketUrl = "ws://" + host + "/mediabrowser"
|
websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
|
||||||
self.logMsg("WebSocket URL : " + webSocketUrl)
|
self.logMsg("websocket URL: %s" % websocketUrl)
|
||||||
self.client = websocket.WebSocketApp(webSocketUrl,
|
|
||||||
|
self.client = websocket.WebSocketApp(websocketUrl,
|
||||||
on_message = self.on_message,
|
on_message = self.on_message,
|
||||||
on_error = self.on_error,
|
on_error = self.on_error,
|
||||||
on_close = self.on_close)
|
on_close = self.on_close)
|
||||||
|
@ -344,5 +266,4 @@ class WebSocketThread(threading.Thread):
|
||||||
pendingItemsToRemove = []
|
pendingItemsToRemove = []
|
||||||
if pendingItemsToUpdate != []:
|
if pendingItemsToUpdate != []:
|
||||||
self.update_items(pendingItemsToUpdate)
|
self.update_items(pendingItemsToUpdate)
|
||||||
pendingItemsToUpdate = []
|
pendingItemsToUpdate = []
|
||||||
|
|
21
service.py
21
service.py
|
@ -35,16 +35,15 @@ class Service():
|
||||||
def __init__(self, *args ):
|
def __init__(self, *args ):
|
||||||
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
|
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
|
||||||
addonName = self.addonName
|
addonName = self.addonName
|
||||||
self.className = self.__class__.__name__
|
|
||||||
|
|
||||||
self.logMsg("Starting Monitor", 0)
|
self.logMsg("Starting Monitor", 0)
|
||||||
self.logMsg("======== START %s ========" % addonName, 0)
|
self.logMsg("======== START %s ========" % addonName, 0)
|
||||||
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel("System.BuildVersion"), 0)
|
self.logMsg("KODI Version: %s" % xbmc.getInfoLabel("System.BuildVersion"), 0)
|
||||||
self.logMsg("%s Version: %s" % (addonName, self.clientInfo.getVersion()), 0)
|
self.logMsg("%s Version: %s" % (addonName, self.clientInfo.getVersion()), 0)
|
||||||
pass
|
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
self.className = self.__class__.__name__
|
||||||
utils.logMsg("%s %s" % (self.addonName, self.className), str(msg), int(lvl))
|
utils.logMsg("%s %s" % (self.addonName, self.className), str(msg), int(lvl))
|
||||||
|
|
||||||
def ServiceEntryPoint(self):
|
def ServiceEntryPoint(self):
|
||||||
|
@ -77,18 +76,18 @@ class Service():
|
||||||
playTime = xbmc.Player().getTime()
|
playTime = xbmc.Player().getTime()
|
||||||
totalTime = xbmc.Player().getTotalTime()
|
totalTime = xbmc.Player().getTotalTime()
|
||||||
currentFile = xbmc.Player().getPlayingFile()
|
currentFile = xbmc.Player().getPlayingFile()
|
||||||
|
|
||||||
if(player.played_information.get(currentFile) != None):
|
if(player.played_information.get(currentFile) != None):
|
||||||
player.played_information[currentFile]["currentPosition"] = playTime
|
player.played_information[currentFile]["currentPosition"] = playTime
|
||||||
|
|
||||||
# send update
|
# send update
|
||||||
td = datetime.today() - lastProgressUpdate
|
td = datetime.today() - lastProgressUpdate
|
||||||
secDiff = td.seconds
|
secDiff = td.seconds
|
||||||
if(secDiff > 10):
|
if(secDiff > 3):
|
||||||
try:
|
try:
|
||||||
player.reportPlayback()
|
player.reportPlayback()
|
||||||
except Exception, msg:
|
except Exception, msg:
|
||||||
xbmc.log("MB3 Sync Service -> Exception reporting progress : " + msg)
|
self.logMsg("Exception reporting progress: %s" % msg)
|
||||||
pass
|
pass
|
||||||
lastProgressUpdate = datetime.today()
|
lastProgressUpdate = datetime.today()
|
||||||
# only try autoplay when there's 20 seconds or less remaining and only once!
|
# only try autoplay when there's 20 seconds or less remaining and only once!
|
||||||
|
@ -97,7 +96,7 @@ class Service():
|
||||||
player.autoPlayPlayback()
|
player.autoPlayPlayback()
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
xbmc.log("MB3 Sync Service -> Exception in Playback Monitor Service : " + str(e))
|
self.logMsg("Exception in Playback Monitor Service: %s" % e)
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if (self.newUserClient == None):
|
if (self.newUserClient == None):
|
||||||
|
@ -113,11 +112,11 @@ class Service():
|
||||||
|
|
||||||
#full sync
|
#full sync
|
||||||
if(startupComplete == False):
|
if(startupComplete == False):
|
||||||
xbmc.log("Doing_Db_Sync: syncDatabase (Started)")
|
self.logMsg("Doing_Db_Sync: syncDatabase (Started)")
|
||||||
libSync = librarySync.syncDatabase()
|
libSync = librarySync.syncDatabase()
|
||||||
xbmc.log("Doing_Db_Sync: syncDatabase (Finished) " + str(libSync))
|
self.logMsg("Doing_Db_Sync: syncDatabase (Finished) " + str(libSync))
|
||||||
countSync = librarySync.updatePlayCounts()
|
countSync = librarySync.updatePlayCounts()
|
||||||
xbmc.log("Doing_Db_Sync: updatePlayCounts (Finished) " + str(countSync))
|
self.logMsg("Doing_Db_Sync: updatePlayCounts (Finished) " + str(countSync))
|
||||||
|
|
||||||
# Force refresh newly set thumbnails
|
# Force refresh newly set thumbnails
|
||||||
xbmc.executebuiltin("UpdateLibrary(video)")
|
xbmc.executebuiltin("UpdateLibrary(video)")
|
||||||
|
@ -130,9 +129,9 @@ class Service():
|
||||||
WebSocketThread().processPendingActions()
|
WebSocketThread().processPendingActions()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
xbmc.log("Not authenticated yet")
|
self.logMsg("Not authenticated yet", 0)
|
||||||
|
|
||||||
utils.logMsg("MB3 Sync Service", "stopping Service",0)
|
self.logMsg("stopping Service", 0)
|
||||||
|
|
||||||
# If user reset library database.
|
# If user reset library database.
|
||||||
WINDOW = xbmcgui.Window(10000)
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
|
Loading…
Reference in a new issue