diff --git a/resources/lib/api.py b/resources/lib/api.py
new file mode 100644
index 00000000..d15822bb
--- /dev/null
+++ b/resources/lib/api.py
@@ -0,0 +1,378 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import clientinfo
+import utils
+
+##################################################################################################
+
+
+class API():
+
+    def __init__(self, item):
+
+        self.item = item
+        self.clientinfo = clientinfo.ClientInfo()
+        self.addonName = self.clientinfo.getAddonName()
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def getUserData(self):
+        # Default
+        favorite = False
+        playcount = None
+        played = False
+        lastPlayedDate = None
+        resume = 0
+        rating = 0
+
+        try:
+            userdata = self.item['UserData']
+        
+        except KeyError: # No userdata found.
+            pass
+
+        else:
+            favorite = userdata['IsFavorite']
+            likes = userdata.get('Likes')
+            # Rating for album and songs
+            if favorite:
+                rating = 5
+            elif likes:
+                rating = 3
+            elif likes == False:
+                rating = 1
+            else:
+                rating = 0
+
+            lastPlayedDate = userdata.get('LastPlayedDate')
+            if lastPlayedDate:
+                lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
+            
+            if userdata['Played']:
+                # Playcount is tied to the watch status
+                played = True
+                playcount = userdata['PlayCount']
+                if playcount == 0:
+                    playcount = 1
+
+                if lastPlayedDate is None:
+                    lastPlayedDate = self.getDateCreated()
+
+            playbackPosition = userdata.get('PlaybackPositionTicks')
+            if playbackPosition:
+                resume = playbackPosition / 10000000.0
+
+        return {
+
+            'Favorite': favorite,
+            'PlayCount': playcount,
+            'Played': played,
+            'LastPlayedDate': lastPlayedDate,
+            'Resume': resume,
+            'Rating': rating
+        }
+
+    def getPeople(self):
+        # Process People
+        director = []
+        writer = []
+        cast = []
+
+        try:
+            people = self.item['People']
+        
+        except KeyError:
+            pass
+        
+        else:
+            for person in people:
+
+                type = person['Type']
+                name = person['Name']
+
+                if "Director" in type:
+                    director.append(name)
+                elif "Actor" in type:
+                    cast.append(name)
+                elif type in ("Writing", "Writer"):
+                    writer.append(name)
+
+        return {
+
+            'Director': director,
+            'Writer': writer,
+            'Cast': cast
+        }
+
+    def getMediaStreams(self):
+        item = self.item
+        videotracks = []
+        audiotracks = []
+        subtitlelanguages = []
+
+        try:
+            media_streams = item['MediaSources'][0]['MediaStreams']
+
+        except KeyError:
+            media_streams = item['MediaStreams']
+
+        for media_stream in media_streams:
+            # Sort through Video, Audio, Subtitle
+            stream_type = media_stream['Type']
+            codec = media_stream.get('Codec', "").lower()
+            profile = media_stream.get('Profile', "").lower()
+
+            if stream_type == "Video":
+                # Height, Width, Codec, AspectRatio, AspectFloat, 3D
+                track = {
+
+                    'videocodec': codec,
+                    'height': media_stream.get('Height'),
+                    'width': media_stream.get('Width'),
+                    'video3DFormat': item.get('Video3DFormat'),
+                    'aspectratio': 1.85
+                }
+
+                try:
+                    container = item['MediaSources'][0]['Container'].lower()
+                except:
+                    container = ""
+
+                # Sort codec vs container/profile
+                if "msmpeg4" in codec:
+                    track['videocodec'] = "divx"
+                elif "mpeg4" in codec:
+                    if "simple profile" in profile or not profile:
+                        track['videocodec'] = "xvid"
+                elif "h264" in codec:
+                    if container in ("mp4", "mov", "m4v"):
+                        track['videocodec'] = "avc1"
+
+                # Aspect ratio
+                if item.get('AspectRatio'):
+                    # Metadata AR
+                    aspectratio = item['AspectRatio']
+                else: # File AR
+                    aspectratio = media_stream.get('AspectRatio', "0")
+
+                try:
+                    aspectwidth, aspectheight = aspectratio.split(':')
+                    track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
+                
+                except ValueError:
+                    width = track['width']
+                    height = track['height']
+
+                    if width and height:
+                        track['aspectratio'] = round(float(width / height), 6)
+
+                videotracks.append(track)
+
+            elif stream_type == "Audio":
+                # Codec, Channels, language
+                track = {
+                    
+                    'audiocodec': codec,
+                    'channels': media_stream.get('Channels'),
+                    'audiolanguage': media_stream.get('Language')
+                }
+
+                if "dca" in codec and "dts-hd ma" in profile:
+                    track['audiocodec'] = "dtshd_ma"
+
+                audiotracks.append(track)
+
+            elif stream_type == "Subtitle":
+                # Language
+                subtitlelanguages.append(media_stream.get('Language', "Unknown"))
+
+        return {
+
+            'video': videotracks, 
+            'audio': audiotracks,
+            'subtitle': subtitlelanguages
+        }
+
+    def getRuntime(self):
+        item = self.item
+        try:
+            runtime = item['RunTimeTicks'] / 10000000.0
+        
+        except KeyError:
+            runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
+
+        return runtime
+
+    def adjustResume(self, resume_seconds):
+
+        resume = 0
+        if resume_seconds:
+            resume = round(float(resume_seconds), 6)
+            jumpback = int(utils.settings('resumeJumpBack'))
+            if resume > jumpback:
+                # To avoid negative bookmark
+                resume = resume - jumpback
+
+        return resume
+
+    def getStudios(self):
+        # Process Studios
+        item = self.item
+        studios = []
+
+        try:
+            studio = item['SeriesStudio']
+            studios.append(self.verifyStudio(studio))
+        
+        except KeyError:
+            studioList = item['Studios']
+            for studio in studioList:
+
+                name = studio['Name']
+                studios.append(self.verifyStudio(name))
+        
+        return studios
+
+    def verifyStudio(self, studioName):
+        # Convert studio for Kodi to properly detect them
+        studios = {
+
+            'abc (us)': "ABC",
+            'fox (us)': "FOX",
+            'mtv (us)': "MTV",
+            'showcase (ca)': "Showcase",
+            'wgn america': "WGN"
+        }
+
+        return studios.get(studioName.lower(), studioName)
+
+    def getChecksum(self):
+        # Use the etags checksum and userdata
+        item = self.item
+        userdata = item['UserData']
+
+        checksum = "%s%s%s%s%s%s" % (
+            
+            item['Etag'], 
+            userdata['Played'],
+            userdata['IsFavorite'],
+            userdata['PlaybackPositionTicks'],
+            userdata.get('UnplayedItemCount', ""),
+            userdata.get('LastPlayedDate', "")
+        )
+
+        return checksum
+
+    def getGenres(self):
+        item = self.item
+        all_genres = ""
+        genres = item.get('Genres', item.get('SeriesGenres'))
+
+        if genres:
+            all_genres = " / ".join(genres)
+
+        return all_genres
+
+    def getDateCreated(self):
+
+        try:
+            dateadded = self.item['DateCreated']
+            dateadded = dateadded.split('.')[0].replace('T', " ")
+        except KeyError:
+            dateadded = None
+
+        return dateadded
+
+    def getPremiereDate(self):
+
+        try:
+            premiere = self.item['PremiereDate']
+            premiere = premiere.split('.')[0].replace('T', " ")
+        except KeyError:
+            premiere = None
+
+        return premiere
+
+    def getOverview(self):
+
+        try:
+            overview = self.item['Overview']
+            overview = overview.replace("\"", "\'")
+            overview = overview.replace("\n", " ")
+            overview = overview.replace("\r", " ")
+        except KeyError:
+            overview = ""
+
+        return overview
+
+    def getTagline(self):
+
+        try:
+            tagline = self.item['Taglines'][0]
+        except IndexError:
+            tagline = None
+
+        return tagline
+
+    def getProvider(self, providername):
+
+        try:
+            provider = self.item['ProviderIds'][providername]
+        except KeyError:
+            provider = None
+
+        return provider
+
+    def getMpaa(self):
+        # Convert more complex cases
+        mpaa = self.item.get('OfficialRating', "")
+        
+        if mpaa in ("NR", "UR"):
+            # Kodi seems to not like NR, but will accept Not Rated
+            mpaa = "Not Rated"
+
+        return mpaa
+
+    def getCountry(self):
+
+        try:
+            country = self.item['ProductionLocations'][0]
+        except IndexError:
+            country = None
+
+        return country
+
+    def getFilePath(self):
+
+        item = self.item
+        try:
+            filepath = item['Path']
+
+        except KeyError:
+            filepath = ""
+
+        else:
+            if "\\\\" in filepath:
+                # append smb protocol
+                filepath = filepath.replace("\\\\", "smb://")
+                filepath = filepath.replace("\\", "/")
+
+            if item.get('VideoType'):
+                videotype = item['VideoType']
+                # Specific format modification
+                if 'Dvd'in videotype:
+                    filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
+                elif 'Bluray' in videotype:
+                    filepath = "%s/BDMV/index.bdmv" % filepath
+            
+            if "\\" in filepath:
+                # Local path scenario, with special videotype
+                filepath = filepath.replace("/", "\\")
+
+        return filepath
\ No newline at end of file
diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py
new file mode 100644
index 00000000..5c47144d
--- /dev/null
+++ b/resources/lib/downloadutils.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import json
+import requests
+import logging
+
+import xbmc
+import xbmcgui
+
+import utils
+import clientinfo
+
+##################################################################################################
+
+# Disable requests logging
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+#logging.getLogger('requests').setLevel(logging.WARNING)
+
+##################################################################################################
+
+
+class DownloadUtils():
+    
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+    clientInfo = clientinfo.ClientInfo()
+    addonName = clientInfo.getAddonName()
+
+    # Requests session
+    s = None
+    timeout = 30
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def setUsername(self, username):
+        # Reserved for userclient only
+        self.username = username
+        self.logMsg("Set username: %s" % username, 2)
+
+    def setUserId(self, userId):
+        # Reserved for userclient only
+        self.userId = userId
+        self.logMsg("Set userId: %s" % userId, 2)
+
+    def setServer(self, server):
+        # Reserved for userclient only
+        self.server = server
+        self.logMsg("Set server: %s" % server, 2)
+
+    def setToken(self, token):
+        # Reserved for userclient only
+        self.token = token
+        self.logMsg("Set token: %s" % token, 2)
+
+    def setSSL(self, ssl, sslclient):
+        # Reserved for userclient only
+        self.sslverify = ssl
+        self.sslclient = sslclient
+        self.logMsg("Verify SSL host certificate: %s" % ssl, 2)
+        self.logMsg("SSL client side certificate: %s" % sslclient, 2)
+
+
+    def postCapabilities(self, deviceId):
+
+        # Post settings to session
+        url = "{server}/emby/Sessions/Capabilities/Full?format=json"
+        data = {
+            
+            'PlayableMediaTypes': "Audio,Video",
+            'SupportsMediaControl': True,
+            'SupportedCommands': (
+                
+                "MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
+                "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
+                "GoHome,PageUp,NextLetter,GoToSearch,"
+                "GoToSettings,PageDown,PreviousLetter,TakeScreenshot,"
+                "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage,"
+                "SetAudioStreamIndex,SetSubtitleStreamIndex,"
+
+                "Mute,Unmute,SetVolume,"
+                "Play,Playstate,PlayNext"
+            )
+        }
+
+        self.logMsg("Capabilities URL: %s" % url, 2)
+        self.logMsg("Postdata: %s" % data, 2)
+
+        self.downloadUrl(url, postBody=data, type="POST")
+        self.logMsg("Posted capabilities to %s" % self.server, 2)
+
+        # Attempt at getting sessionId
+        url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
+        result = self.downloadUrl(url)
+        try:
+            sessionId = result[0]['Id']
+        
+        except (KeyError, TypeError):
+            self.logMsg("Failed to retrieve sessionId.", 1)
+        
+        else:
+            self.logMsg("Session: %s" % result, 2)
+            self.logMsg("SessionId: %s" % sessionId, 1)
+            utils.window('emby_sessionId', value=sessionId)
+            
+            # Post any permanent additional users
+            additionalUsers = utils.settings('additionalUsers')
+            if additionalUsers:
+                
+                additionalUsers = additionalUsers.split(',')
+                self.logMsg(
+                    "List of permanent users added to the session: %s"
+                    % additionalUsers, 1)
+
+                # Get the user list from server to get the userId
+                url = "{server}/emby/Users?format=json"
+                result = self.downloadUrl(url)
+
+                for additional in additionalUsers:
+                    addUser = additional.decode('utf-8').lower()
+
+                    # Compare to server users to list of permanent additional users
+                    for user in result:
+                        username = user['Name'].lower()
+
+                        if username in addUser:
+                            userId = user['Id']
+                            url = (
+                                    "{server}/emby/Sessions/%s/Users/%s?format=json"
+                                    % (sessionId, userId)
+                            )
+                            self.downloadUrl(url, postBody={}, type="POST")
+
+
+    def startSession(self):
+
+        self.deviceId = self.clientInfo.getDeviceId()
+
+        # User is identified from this point
+        # Attach authenticated header to the session
+        verify = None
+        cert = None
+        header = self.getHeader()
+
+        # If user enabled host certificate verification
+        try:
+            verify = self.sslverify
+            cert = self.sslclient
+        except:
+            self.logMsg("Could not load SSL settings.", 1)
+        
+        # Start session
+        self.s = requests.Session()
+        self.s.headers = header
+        self.s.verify = verify
+        self.s.cert = cert
+        # Retry connections to the server
+        self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
+        self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
+
+        self.logMsg("Requests session started on: %s" % self.server, 1)
+
+    def stopSession(self):
+        try:
+            self.s.close()
+        except:
+            self.logMsg("Requests session could not be terminated.", 1)
+
+    def getHeader(self, authenticate=True):
+
+        clientInfo = self.clientInfo
+
+        deviceName = clientInfo.getDeviceName()
+        deviceId = clientInfo.getDeviceId()
+        version = clientInfo.getVersion()
+
+        if not authenticate:
+            # If user is not authenticated
+            auth = (
+                'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
+                % (deviceName, deviceId, version))
+            header = {
+
+                'Content-type': 'application/json',
+                'Accept-encoding': 'gzip',
+                'Accept-Charset': 'UTF-8,*',
+                'Authorization': auth
+            }      
+            self.logMsg("Header: %s" % header, 2)
+        
+        else:
+            userId = self.userId
+            token = self.token
+            # Attached to the requests session
+            auth = (
+                'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
+                % (userId, deviceName, deviceId, version))
+            header = {
+
+                'Content-type': 'application/json',
+                'Accept-encoding': 'gzip',
+                'Accept-Charset': 'UTF-8,*',
+                'Authorization': auth,
+                'X-MediaBrowser-Token': token
+            }        
+            self.logMsg("Header: %s" % header, 2)
+        
+        return header
+
+    def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
+        
+        self.logMsg("=== ENTER downloadUrl ===", 2)
+
+        timeout = self.timeout
+        default_link = ""
+
+        try:
+            # If user is authenticated
+            if (authenticate):
+                # Get requests session
+                try: 
+                    s = self.s
+                    # Replace for the real values
+                    url = url.replace("{server}", self.server)
+                    url = url.replace("{UserId}", self.userId)
+
+                    # Prepare request
+                    if type == "GET":
+                        r = s.get(url, json=postBody, params=parameters, timeout=timeout)
+                    elif type == "POST":
+                        r = s.post(url, json=postBody, timeout=timeout)
+                    elif type == "DELETE":
+                        r = s.delete(url, json=postBody, timeout=timeout)
+                
+                except AttributeError:
+                    # request session does not exists
+                    # Get user information
+                    self.userId = utils.window('emby_currUser')
+                    self.server = utils.window('emby_server%s' % self.userId)
+                    self.token = utils.window('emby_accessToken%s' % self.userId)
+                    header = self.getHeader()
+                    verifyssl = False
+                    cert = None
+
+                    # IF user enables ssl verification
+                    if utils.settings('sslverify') == "true":
+                        verifyssl = True
+                    if utils.settings('sslcert') != "None":
+                        cert = utils.settings('sslcert')
+
+                    # Replace for the real values
+                    url = url.replace("{server}", self.server)
+                    url = url.replace("{UserId}", self.userId)
+
+                    # Prepare request
+                    if type == "GET":
+                        r = requests.get(url,
+                                        json=postBody,
+                                        params=parameters,
+                                        headers=header,
+                                        timeout=timeout,
+                                        cert=cert,
+                                        verify=verifyssl)
+
+                    elif type == "POST":
+                        r = requests.post(url,
+                                        json=postBody,
+                                        headers=header,
+                                        timeout=timeout,
+                                        cert=cert,
+                                        verify=verifyssl)
+
+                    elif type == "DELETE":
+                        r = requests.delete(url,
+                                        json=postBody,
+                                        headers=header,
+                                        timeout=timeout,
+                                        cert=cert,
+                                        verify=verifyssl)
+
+            # If user is not authenticated
+            elif not authenticate:
+
+                header = self.getHeader(authenticate=False)
+                verifyssl = False
+
+                # If user enables ssl verification
+                try:
+                    verifyssl = self.sslverify
+                except AttributeError:
+                    pass
+                
+                # Prepare request
+                if type == "GET":
+                    r = requests.get(url,
+                                    json=postBody,
+                                    params=parameters,
+                                    headers=header,
+                                    timeout=timeout,
+                                    verify=verifyssl)
+
+                elif type == "POST":
+                    r = requests.post(url,
+                                    json=postBody,
+                                    headers=header,
+                                    timeout=timeout,
+                                    verify=verifyssl)
+        
+            ##### THE RESPONSE #####
+            self.logMsg(r.url, 2)
+            if r.status_code == 204:
+                # No body in the response
+                self.logMsg("====== 204 Success ======", 2)
+
+            elif r.status_code == requests.codes.ok:
+               
+                try: 
+                    # UTF-8 - JSON object
+                    r = r.json()
+                    self.logMsg("====== 200 Success ======", 2)
+                    self.logMsg("Response: %s" % r, 2)
+                    return r
+
+                except:
+                    if r.headers.get('content-type') != "text/html":
+                        self.logMsg("Unable to convert the response for: %s" % url, 1)
+            else:
+                r.raise_for_status()
+        
+        ##### EXCEPTIONS #####
+
+        except requests.exceptions.ConnectionError as e:
+            # Make the addon aware of status
+            if utils.window('emby_online') != "false":
+                self.logMsg("Server unreachable at: %s" % url, 0)
+                self.logMsg(e, 2)
+                utils.window('emby_online', value="false")
+
+        except requests.exceptions.ConnectTimeout as e:
+            self.logMsg("Server timeout at: %s" % url, 0)
+            self.logMsg(e, 1)
+
+        except requests.exceptions.HTTPError as e:
+
+            if r.status_code == 401:
+                # Unauthorized
+                status = utils.window('emby_serverStatus')
+
+                if 'X-Application-Error-Code' in r.headers:
+                    # Emby server errors
+                    if r.headers['X-Application-Error-Code'] == "ParentalControl":
+                        # Parental control - access restricted
+                        utils.window('emby_serverStatus', value="restricted")
+                        xbmcgui.Dialog().notification(
+                                                heading="Emby server",
+                                                message="Access restricted.",
+                                                icon=xbmcgui.NOTIFICATION_ERROR,
+                                                time=5000)
+                        return False
+                    
+                    elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
+                        # User tried to do something his emby account doesn't allow
+                        pass
+
+                elif status not in ("401", "Auth"):
+                    # Tell userclient token has been revoked.
+                    utils.window('emby_serverStatus', value="401")
+                    self.logMsg("HTTP Error: %s" % e, 0)
+                    xbmcgui.Dialog().notification(
+                                            heading="Error connecting",
+                                            message="Unauthorized.",
+                                            icon=xbmcgui.NOTIFICATION_ERROR)
+                    return 401
+
+            elif r.status_code in (301, 302):
+                # Redirects
+                pass
+            elif r.status_code == 400:
+                # Bad requests
+                pass
+
+        except requests.exceptions.SSLError as e:
+            self.logMsg("Invalid SSL certificate for: %s" % url, 0)
+            self.logMsg(e, 1)
+
+        except requests.exceptions.RequestException as e:
+            self.logMsg("Unknown error connecting to: %s" % url, 0)
+            self.logMsg(e, 1)
+
+        return default_link
\ No newline at end of file
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
new file mode 100644
index 00000000..588b8d3c
--- /dev/null
+++ b/resources/lib/entrypoint.py
@@ -0,0 +1,842 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import os
+import sys
+import urlparse
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+import xbmcplugin
+
+import artwork
+import utils
+import clientinfo
+import downloadutils
+import read_embyserver as embyserver
+import embydb_functions as embydb
+import playlist
+import playbackutils as pbutils
+import playutils
+import api
+
+#################################################################################################
+
+
+def doPlayback(itemid, dbid):
+
+    emby = embyserver.Read_EmbyServer()
+    item = emby.getItem(itemid)
+    pbutils.PlaybackUtils(item).play(itemid, dbid)
+
+##### DO RESET AUTH #####
+def resetAuth():
+    # User tried login and failed too many times
+    resp = xbmcgui.Dialog().yesno(
+                heading="Warning",
+                line1=(
+                    "Emby might lock your account if you fail to log in too many times. "
+                    "Proceed anyway?"))
+    if resp == 1:
+        utils.logMsg("EMBY", "Reset login attempts.", 1)
+        utils.window('emby_serverStatus', value="Auth")
+    else:
+        xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
+
+def addDirectoryItem(label, path, folder=True):
+    li = xbmcgui.ListItem(label, path=path)
+    li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
+    li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
+    li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
+    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
+
+def doMainListing():
+    
+    xbmcplugin.setContent(int(sys.argv[1]), 'files')    
+    # Get emby nodes from the window props
+    embyprops = utils.window('Emby.nodes.total')
+    if embyprops:
+        totalnodes = int(embyprops)
+        for i in range(totalnodes):
+            path = utils.window('Emby.nodes.%s.index' % i)
+            if not path:
+                path = utils.window('Emby.nodes.%s.content' % i)
+            label = utils.window('Emby.nodes.%s.title' % i)
+            if path:
+                addDirectoryItem(label, path)
+    
+    # some extra entries for settings and stuff. TODO --> localize the labels
+    addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords", False)
+    addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings", False)
+    addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser", False)
+    #addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
+    addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync", False)
+    addDirectoryItem(
+        label="Repair local database (force update all content)",
+        path="plugin://plugin.video.emby/?mode=repair",
+        folder=False)
+    addDirectoryItem(
+        label="Perform local database reset (full resync)",
+        path="plugin://plugin.video.emby/?mode=reset",
+        folder=False)
+    addDirectoryItem(
+        label="Sync Emby Theme Media to Kodi",
+        path="plugin://plugin.video.emby/?mode=thememedia",
+        folder=False)
+    
+    xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+##### ADD ADDITIONAL USERS #####
+def addUser():
+
+    doUtils = downloadutils.DownloadUtils()
+    clientInfo = clientinfo.ClientInfo()
+    deviceId = clientInfo.getDeviceId()
+    deviceName = clientInfo.getDeviceName()
+    userid = utils.window('emby_currUser')
+    dialog = xbmcgui.Dialog()
+
+    # Get session
+    url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
+    result = doUtils.downloadUrl(url)
+    
+    try:
+        sessionId = result[0]['Id']
+        additionalUsers = result[0]['AdditionalUsers']
+        # Add user to session
+        userlist = {}
+        users = []
+        url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json"
+        result = doUtils.downloadUrl(url)
+
+        # pull the list of users
+        for user in result:
+            name = user['Name']
+            userId = user['Id']
+            if userid != userId:
+                userlist[name] = userId
+                users.append(name)
+
+        # Display dialog if there's additional users
+        if additionalUsers:
+
+            option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
+            # Users currently in the session
+            additionalUserlist = {}
+            additionalUsername = []
+            # Users currently in the session
+            for user in additionalUsers:
+                name = user['UserName']
+                userId = user['UserId']
+                additionalUserlist[name] = userId
+                additionalUsername.append(name)
+
+            if option == 1:
+                # User selected Remove user
+                resp = dialog.select("Remove user from the session", additionalUsername)
+                if resp > -1:
+                    selected = additionalUsername[resp]
+                    selected_userId = additionalUserlist[selected]
+                    url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+                    doUtils.downloadUrl(url, postBody={}, type="DELETE")
+                    dialog.notification(
+                            heading="Success!",
+                            message="%s removed from viewing session" % selected,
+                            icon="special://home/addons/plugin.video.emby/icon.png",
+                            time=1000)
+
+                    # clear picture
+                    position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
+                    utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
+                    return
+                else:
+                    return
+
+            elif option == 0:
+                # User selected Add user
+                for adduser in additionalUsername:
+                    try: # Remove from selected already added users. It is possible they are hidden.
+                        users.remove(adduser)
+                    except: pass
+
+            elif option < 0:
+                # User cancelled
+                return
+
+        # Subtract any additional users
+        utils.logMsg("EMBY", "Displaying list of users: %s" % users)
+        resp = dialog.select("Add user to the session", users)
+        # post additional user
+        if resp > -1:
+            selected = users[resp]
+            selected_userId = userlist[selected]
+            url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+            doUtils.downloadUrl(url, postBody={}, type="POST")
+            dialog.notification(
+                    heading="Success!",
+                    message="%s added to viewing session" % selected,
+                    icon="special://home/addons/plugin.video.emby/icon.png",
+                    time=1000)
+
+    except:
+        utils.logMsg("EMBY", "Failed to add user to session.")
+        dialog.notification(
+                heading="Error",
+                message="Unable to add/remove user from the session.",
+                icon=xbmcgui.NOTIFICATION_ERROR)
+
+    # Add additional user images
+    # always clear the individual items first
+    totalNodes = 10
+    for i in range(totalNodes):
+        if not utils.window('EmbyAdditionalUserImage.%s' % i):
+            break
+        utils.window('EmbyAdditionalUserImage.%s' % i)
+
+    url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
+    result = doUtils.downloadUrl(url)
+    additionalUsers = result[0]['AdditionalUsers']
+    count = 0
+    for additionaluser in additionalUsers:
+        url = "{server}/emby/Users/%s?format=json" % additionaluser['UserId']
+        result = doUtils.downloadUrl(url)
+        utils.window('EmbyAdditionalUserImage.%s' % count,
+            value=artwork.Artwork().getUserArtwork(result, 'Primary'))
+        utils.window('EmbyAdditionalUserPosition.%s' % additionaluser['UserId'], value=str(count))
+        count +=1
+
+##### THEME MUSIC/VIDEOS #####
+def getThemeMedia():
+
+    doUtils = downloadutils.DownloadUtils()
+    dialog = xbmcgui.Dialog()
+    playback = None
+
+    # Choose playback method
+    resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
+    if resp == 0:
+        playback = "DirectPlay"
+    elif resp == 1:
+        playback = "DirectStream"
+    else:
+        return
+
+    library = xbmc.translatePath(
+                "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
+    # Create library directory
+    if not xbmcvfs.exists(library):
+        xbmcvfs.mkdir(library)
+
+    # Set custom path for user
+    tvtunes_path = xbmc.translatePath(
+        "special://profile/addon_data/script.tvtunes/").decode('utf-8')
+    if xbmcvfs.exists(tvtunes_path):
+        tvtunes = xbmcaddon.Addon(id="script.tvtunes")
+        tvtunes.setSetting('custom_path_enable', "true")
+        tvtunes.setSetting('custom_path', library)
+        utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
+    else:
+        # if it does not exist this will not work so warn user
+        # often they need to edit the settings first for it to be created.
+        dialog.ok(
+            heading="Warning",
+            line1=(
+                "The settings file does not exist in tvtunes. ",
+                "Go to the tvtunes addon and change a setting, then come back and re-run."))
+        xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
+        return
+        
+    # Get every user view Id
+    embyconn = utils.kodiSQL('emby')
+    embycursor = embyconn.cursor()
+    emby_db = embydb.Embydb_Functions(embycursor)
+    viewids = emby_db.getViews()
+    embycursor.close()
+
+    # Get Ids with Theme Videos
+    itemIds = {}
+    for view in viewids:
+        url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
+        result = doUtils.downloadUrl(url)
+        if result['TotalRecordCount'] != 0:
+            for item in result['Items']:
+                itemId = item['Id']
+                folderName = item['Name']
+                folderName = utils.normalize_string(folderName.encode('utf-8'))
+                itemIds[itemId] = folderName
+
+    # Get paths for theme videos
+    for itemId in itemIds:
+        nfo_path = xbmc.translatePath(
+            "special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
+        # Create folders for each content
+        if not xbmcvfs.exists(nfo_path):
+            xbmcvfs.mkdir(nfo_path)
+        # Where to put the nfos
+        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
+
+        url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # Create nfo and write themes to it
+        nfo_file = open(nfo_path, 'w')
+        pathstowrite = ""
+        # May be more than one theme
+        for theme in result['Items']:
+            putils = playutils.PlayUtils(theme)
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+        
+        # Check if the item has theme songs and add them   
+        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # May be more than one theme
+        for theme in result['Items']:
+            putils = playutils.PlayUtils(theme)  
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+
+        nfo_file.write(
+            '<tvtunes>%s</tvtunes>' % pathstowrite
+        )
+        # Close nfo file
+        nfo_file.close()
+
+    # Get Ids with Theme songs
+    musicitemIds = {}
+    for view in viewids:
+        url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
+        result = doUtils.downloadUrl(url)
+        if result['TotalRecordCount'] != 0:
+            for item in result['Items']:
+                itemId = item['Id']
+                folderName = item['Name']
+                folderName = utils.normalize_string(folderName.encode('utf-8'))
+                musicitemIds[itemId] = folderName
+
+    # Get paths
+    for itemId in musicitemIds:
+        
+        # if the item was already processed with video themes back out
+        if itemId in itemIds:
+            continue
+        
+        nfo_path = xbmc.translatePath(
+            "special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
+        # Create folders for each content
+        if not xbmcvfs.exists(nfo_path):
+            xbmcvfs.mkdir(nfo_path)
+        # Where to put the nfos
+        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
+        
+        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # Create nfo and write themes to it
+        nfo_file = open(nfo_path, 'w')
+        pathstowrite = ""
+        # May be more than one theme
+        for theme in result['Items']: 
+            putils = playutils.PlayUtils(theme)
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+
+        nfo_file.write(
+            '<tvtunes>%s</tvtunes>' % pathstowrite
+        )
+        # Close nfo file
+        nfo_file.close()
+
+    dialog.notification(
+            heading="Emby for Kodi",
+            message="Themes added!",
+            icon="special://home/addons/plugin.video.emby/icon.png",
+            time=1000,
+            sound=False)
+
+##### BROWSE EMBY CHANNELS #####    
+def BrowseChannels(itemid, folderid=None):
+    
+    _addon_id   =   int(sys.argv[1])
+    _addon_url  =   sys.argv[0]
+    doUtils = downloadutils.DownloadUtils()
+    art = artwork.Artwork()
+
+    xbmcplugin.setContent(int(sys.argv[1]), 'files')
+    if folderid:
+        url = (
+                "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json"
+                % (itemid, folderid))
+    elif itemid == "0":
+        # id 0 is the root channels folder
+        url = "{server}/emby/Channels?{UserId}&format=json"
+    else:
+        url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
+
+    result = doUtils.downloadUrl(url)
+    try:
+        channels = result['Items']
+    except TypeError:
+        pass
+    else:
+        for item in channels:
+
+            API = api.API(item)
+            itemid = item['Id']
+            itemtype = item['Type']
+            title = item.get('Name', "Missing Title")
+            li = xbmcgui.ListItem(title)
+
+            if itemtype == "ChannelFolderItem":
+                isFolder = True
+            else:
+                isFolder = False
+
+            channelId = item.get('ChannelId', "")
+            channelName = item.get('ChannelName', "")
+
+            premieredate = API.getPremiereDate()
+            # Process Genres
+            genre = API.getGenres()
+            # Process UserData
+            overlay = 0
+
+            userdata = API.getUserData()
+            seektime = userdata['Resume']
+            played = userdata['Played']
+            if played:
+                overlay = 7
+            else:
+                overlay = 6
+
+            favorite = userdata['Favorite']
+            if favorite:
+                overlay = 5
+            
+            playcount = userdata['PlayCount']
+            if playcount is None:
+                playcount = 0
+
+            # Populate the details list
+            details = {
+
+                'title': title,
+                'channelname': channelName,
+                'plot': API.getOverview(),
+                'Overlay': str(overlay),
+                'playcount': str(playcount)
+            }
+
+            if itemtype == "ChannelVideoItem":
+                xbmcplugin.setContent(_addon_id, 'movies')
+            elif itemtype == "ChannelAudioItem":
+                xbmcplugin.setContent(_addon_id, 'songs')
+
+            # Populate the extradata list and artwork
+            pbutils.PlaybackUtils(item).setArtwork(li)
+            extradata = {
+
+                'id': itemid,
+                'rating': item.get('CommunityRating'),
+                'year': item.get('ProductionYear'),
+                'premieredate': premieredate,
+                'genre': genre,
+                'playcount': str(playcount),
+                'itemtype': itemtype
+            }
+            li.setInfo('video', infoLabels=extradata)
+            li.setThumbnailImage(art.getAllArtwork(item)['Primary'])
+            li.setIconImage('DefaultTVShows.png')
+
+            if itemtype == "Channel":
+                path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
+                xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
+            
+            elif isFolder:
+                path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
+                xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
+            else:
+                path = "%s?id=%s&mode=play" % (_addon_url, itemid)
+                li.setProperty('IsPlayable', 'true')
+                xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li)
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### LISTITEM SETUP FOR VIDEONODES #####
+def createListItem(item):
+
+    title = item['title']
+    li = xbmcgui.ListItem(title)
+    li.setProperty('IsPlayable', "true")
+    
+    metadata = {
+
+        'Title': title,
+        'duration': str(item['runtime']/60),
+        'Plot': item['plot'],
+        'Playcount': item['playcount']
+    }
+
+    if "episode" in item:
+        episode = item['episode']
+        metadata['Episode'] = episode
+
+    if "season" in item:
+        season = item['season']
+        metadata['Season'] = season
+
+    if season and episode:
+        li.setProperty('episodeno', "s%.2de%.2d" % (season, episode))
+
+    if "firstaired" in item:
+        metadata['Premiered'] = item['firstaired']
+
+    if "showtitle" in item:
+        metadata['TVshowTitle'] = item['showtitle']
+
+    if "rating" in item:
+        metadata['Rating'] = str(round(float(item['rating']),1))
+
+    if "director" in item:
+        metadata['Director'] = " / ".join(item['director'])
+
+    if "writer" in item:
+        metadata['Writer'] = " / ".join(item['writer'])
+
+    if "cast" in item:
+        cast = []
+        castandrole = []
+        for person in item['cast']:
+            name = person['name']
+            cast.append(name)
+            castandrole.append((name, person['role']))
+        metadata['Cast'] = cast
+        metadata['CastAndRole'] = castandrole
+
+    li.setInfo(type="Video", infoLabels=metadata)  
+    li.setProperty('resumetime', str(item['resume']['position']))
+    li.setProperty('totaltime', str(item['resume']['total']))
+    li.setArt(item['art'])
+    li.setThumbnailImage(item['art'].get('thumb',''))
+    li.setIconImage('DefaultTVShows.png')
+    li.setProperty('dbid', str(item['episodeid']))
+    li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
+    for key, value in item['streamdetails'].iteritems():
+        for stream in value:
+            li.addStreamInfo(key, stream)
+    
+    return li
+
+##### GET NEXTUP EPISODES FOR TAGNAME #####    
+def getNextUpEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with nextup parameter,
+    # we return the nextepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "lastplayed"},
+            'filter': {
+                'and': [
+                    {'operator': "true", 'field': "inprogress", 'value': ""},
+                    {'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
+                ]},
+            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        for item in items:
+            if utils.settings('ignoreSpecialsNextEpisodes') == "true":
+                query = {
+
+                    'jsonrpc': "2.0",
+                    'id': 1,
+                    'method': "VideoLibrary.GetEpisodes",
+                    'params': {
+
+                        'tvshowid': item['tvshowid'],
+                        'sort': {'method': "episode"},
+                        'filter': {
+                            'and': [
+                                {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                                {'operator': "greaterthan", 'field': "season", 'value': "0"}
+                        ]},
+                        'properties': [
+                            "title", "playcount", "season", "episode", "showtitle",
+                            "plot", "file", "rating", "resume", "tvshowid", "art",
+                            "streamdetails", "firstaired", "runtime", "writer",
+                            "dateadded", "lastplayed"
+                        ],
+                        'limits': {"end": 1}
+                    }
+                }
+            else:
+                query = {
+
+                    'jsonrpc': "2.0",
+                    'id': 1,
+                    'method': "VideoLibrary.GetEpisodes",
+                    'params': {
+
+                        'tvshowid': item['tvshowid'],
+                        'sort': {'method': "episode"},
+                        'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                        'properties': [
+                            "title", "playcount", "season", "episode", "showtitle",
+                            "plot", "file", "rating", "resume", "tvshowid", "art",
+                            "streamdetails", "firstaired", "runtime", "writer",
+                            "dateadded", "lastplayed"
+                        ],
+                        'limits': {"end": 1}
+                    }
+                }
+
+            result = xbmc.executeJSONRPC(json.dumps(query))
+            result = json.loads(result)
+            try:
+                episodes = result['result']['episodes']
+            except (KeyError, TypeError):
+                pass
+            else:
+                for episode in episodes:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=item['file'],
+                                listitem=li)
+                    count += 1
+
+            if count == limit:
+                break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET INPROGRESS EPISODES FOR TAGNAME #####    
+def getInProgressEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with inprogressepisodes parameter,
+    # we return the inprogressepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the in-progress TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "lastplayed"},
+            'filter': {
+                'and': [
+                    {'operator': "true", 'field': "inprogress", 'value': ""},
+                    {'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
+                ]},
+            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        for item in items:
+            query = {
+
+                'jsonrpc': "2.0",
+                'id': 1,
+                'method': "VideoLibrary.GetEpisodes",
+                'params': {
+
+                    'tvshowid': item['tvshowid'],
+                    'sort': {'method': "episode"},
+                    'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
+                    'properties': [
+                        "title", "playcount", "season", "episode", "showtitle", "plot",
+                        "file", "rating", "resume", "tvshowid", "art", "cast",
+                        "streamdetails", "firstaired", "runtime", "writer",
+                        "dateadded", "lastplayed"
+                    ]
+                }
+            }
+            result = xbmc.executeJSONRPC(json.dumps(query))
+            result = json.loads(result)
+            try:
+                episodes = result['result']['episodes']
+            except (KeyError, TypeError):
+                pass
+            else:
+                for episode in episodes:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=item['file'],
+                                listitem=li)
+                    count += 1
+
+            if count == limit:
+                break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET RECENT EPISODES FOR TAGNAME #####    
+def getRecentEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with recentepisodes parameter,
+    # we return the recentepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "dateadded"},
+            'filter': {'operator': "contains", 'field': "tag", 'value': "%s" % tagname},
+            'properties': ["title","sorttitle"]
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        allshowsIds = set()
+        for item in items:
+            allshowsIds.add(item['tvshowid'])
+
+        query = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "VideoLibrary.GetEpisodes",
+            'params': {
+
+                'sort': {'order': "descending", 'method': "dateadded"},
+                'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                'properties': [
+                    "title", "playcount", "season", "episode", "showtitle", "plot",
+                    "file", "rating", "resume", "tvshowid", "art", "streamdetails",
+                    "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed"
+                ],
+                "limits": {"end": limit}
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(query))
+        result = json.loads(result)
+        try:
+            episodes = result['result']['episodes']
+        except (KeyError, TypeError):
+            pass
+        else:
+            for episode in episodes:
+                if episode['tvshowid'] in allshowsIds:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=item['file'],
+                                listitem=li)
+                    count += 1
+
+                if count == limit:
+                    break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET EXTRAFANART FOR LISTITEM #####
+def getExtraFanArt():
+    
+    emby = embyserver.Read_EmbyServer()
+    art = artwork.Artwork()
+    
+    # Get extrafanart for listitem 
+    # this will only be used for skins that actually call the listitem's path + fanart dir... 
+    try:
+        # Only do this if the listitem has actually changed
+        itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath")
+            
+        if not itemPath:
+            itemPath = xbmc.getInfoLabel("ListItem.Path")
+        
+        if any([x in itemPath for x in ['tvshows', 'musicvideos', 'movies']]):
+            params = urlparse.parse_qs(itemPath)
+            embyId = params['id'][0]
+            
+            utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 1)
+
+            # We need to store the images locally for this to work
+            # because of the caching system in xbmc
+            fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8')
+            
+            if not xbmcvfs.exists(fanartDir):
+                # Download the images to the cache directory
+                xbmcvfs.mkdirs(fanartDir)
+                item = emby.getItem(embyId)
+                if item:
+                    backdrops = art.getAllArtwork(item)['Backdrop']
+                    tags = item['BackdropImageTags']
+                    count = 0
+                    for backdrop in backdrops:
+                        # Same ordering as in artwork
+                        tag = tags[count]
+                        fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
+                        li = xbmcgui.ListItem(tag, path=fanartFile)
+                        xbmcplugin.addDirectoryItem(
+                                            handle=int(sys.argv[1]),
+                                            url=fanartFile,
+                                            listitem=li)
+                        xbmcvfs.copy(backdrop, fanartFile) 
+                        count += 1               
+            else:
+                utils.logMsg("EMBY", "Found cached backdrop.", 2)
+                # Use existing cached images
+                dirs, files = xbmcvfs.listdir(fanartDir)
+                for file in files:
+                    fanartFile = os.path.join(fanartDir, file)
+                    li = xbmcgui.ListItem(file, path=fanartFile)
+                    xbmcplugin.addDirectoryItem(
+                                            handle=int(sys.argv[1]),
+                                            url=fanartFile,
+                                            listitem=li)
+    except Exception as e:
+        utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 1)
+    
+    # Always do endofdirectory to prevent errors in the logs
+    xbmcplugin.endOfDirectory(int(sys.argv[1]))
\ No newline at end of file
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
new file mode 100644
index 00000000..1b3f7862
--- /dev/null
+++ b/resources/lib/kodimonitor.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+
+import xbmc
+import xbmcgui
+
+import clientinfo
+import downloadutils
+import embydb_functions as embydb
+import playbackutils as pbutils
+import utils
+
+#################################################################################################
+
+
+class KodiMonitor(xbmc.Monitor):
+
+
+    def __init__(self):
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+
+        self.logMsg("Kodi monitor started.", 1)
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def onScanStarted(self, library):
+        self.logMsg("Kodi library scan %s running." % library, 2)
+        if library == "video":
+            utils.window('emby_kodiScan', value="true")
+            
+    def onScanFinished(self, library):
+        self.logMsg("Kodi library scan %s finished." % library, 2)
+        if library == "video":
+            utils.window('emby_kodiScan', clear=True)
+
+    def onNotification(self, sender, method, data):
+
+        doUtils = self.doUtils
+        if method not in ("Playlist.OnAdd"):
+            self.logMsg("Method: %s Data: %s" % (method, data), 1)
+            
+        if data:
+            data = json.loads(data)
+
+
+        if method == "Player.OnPlay":
+            # Set up report progress for emby playback
+            item = data.get('item')
+            try:
+                kodiid = item['id']
+                type = item['type']
+            except (KeyError, TypeError):
+                self.logMsg("Properties already set for item.", 1)
+            else:
+                if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
+                        (type == "song" and utils.settings('disableMusic') == "false")):
+                    # Set up properties for player
+                    embyconn = utils.kodiSQL('emby')
+                    embycursor = embyconn.cursor()
+                    emby_db = embydb.Embydb_Functions(embycursor)
+                    emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+                    try:
+                        itemid = emby_dbitem[0]
+                    except TypeError:
+                        self.logMsg("No kodiid returned.", 1)
+                    else:
+                        url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
+                        result = doUtils.downloadUrl(url)
+                        self.logMsg("Item: %s" % result, 2)
+
+                        playurl = None
+                        count = 0
+                        while not playurl and count < 2:
+                            try:
+                                playurl = xbmc.Player().getPlayingFile()
+                            except RuntimeError:
+                                count += 1
+                                xbmc.sleep(200)
+                            else:
+                                listItem = xbmcgui.ListItem()
+                                playback = pbutils.PlaybackUtils(result)
+
+                                if type == "song" and utils.settings('streamMusic') == "true":
+                                    utils.window('emby_%s.playmethod' % playurl,
+                                        value="DirectStream")
+                                else:
+                                    utils.window('emby_%s.playmethod' % playurl,
+                                        value="DirectPlay")
+                                # Set properties for player.py
+                                playback.setProperties(playurl, listItem)
+                    finally:
+                        embycursor.close()
+            
+
+        elif method == "VideoLibrary.OnUpdate":
+            # Manually marking as watched/unwatched
+            playcount = data.get('playcount')
+            item = data.get('item')
+            try:
+                kodiid = item['id']
+                type = item['type']
+            except (KeyError, TypeError):
+                self.logMsg("Item is invalid for playstate update.", 1)
+            else:
+                # Send notification to the server.
+                embyconn = utils.kodiSQL('emby')
+                embycursor = embyconn.cursor()
+                emby_db = embydb.Embydb_Functions(embycursor)
+                emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+                try:
+                    itemid = emby_dbitem[0]
+                except TypeError:
+                    self.logMsg("Could not find itemid in emby database.", 1)
+                else:
+                    # Stop from manually marking as watched unwatched, with actual playback.
+                    if utils.window('emby_skipWatched%s' % itemid) == "true":
+                        # property is set in player.py
+                        utils.window('emby_skipWatched%s' % itemid, clear=True)
+                    else:
+                        # notify the server
+                        url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
+                        if playcount != 0:
+                            doUtils.downloadUrl(url, type="POST")
+                            self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
+                        else:
+                            doUtils.downloadUrl(url, type="DELETE")
+                            self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
+                finally:
+                    embycursor.close()
+
+
+        elif method == "VideoLibrary.OnRemove":
+
+            try:
+                kodiid = data['id']
+                type = data['type']
+            except (KeyError, TypeError):
+                self.logMsg("Item is invalid for emby deletion.", 1)
+            else:
+                # Send the delete action to the server.
+                offerDelete = False
+
+                if type == "episode" and utils.settings('deleteTV') == "true":
+                    offerDelete = True
+                elif type == "movie" and utils.settings('deleteMovies') == "true":
+                    offerDelete = True
+
+                if utils.settings('offerDelete') != "true":
+                    # Delete could be disabled, even if the subsetting is enabled.
+                    offerDelete = False
+
+                if offerDelete:
+                    embyconn = utils.kodiSQL('emby')
+                    embycursor = embyconn.cursor()
+                    emby_db = embydb.Embydb_Functions(embycursor)
+                    emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+                    try:
+                        itemid = emby_dbitem[0]
+                    except TypeError:
+                        self.logMsg("Could not find itemid in emby database.", 1)
+                    else:
+                        if utils.settings('skipConfirmDelete') != "true":
+                            resp = xbmcgui.Dialog().yesno(
+                                                    heading="Confirm delete",
+                                                    line1="Delete file on Emby Server?")
+                            if not resp:
+                                self.logMsg("User skipped deletion.", 1)
+                                embycursor.close()
+                                return
+                        url = "{server}/emby/Items/%s?format=json" % itemid
+                        self.logMsg("Deleting request: %s" % itemid)
+                        doUtils.downloadUrl(url, type="DELETE")
+                    finally:
+                        embycursor.close()
+
+
+        elif method == "System.OnWake":
+            # Allow network to wake up
+            xbmc.sleep(10000)
+            utils.window('emby_onWake', value="true")
+
+        elif method == "Playlist.OnClear":
+            utils.window('emby_customPlaylist', clear=True, windowid=10101)
+            #xbmcgui.Window(10101).clearProperties()
+            self.logMsg("Clear playlist properties.")
\ No newline at end of file
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
new file mode 100644
index 00000000..d6d15d8e
--- /dev/null
+++ b/resources/lib/librarysync.py
@@ -0,0 +1,1338 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import sqlite3
+import threading
+from datetime import datetime, timedelta, time
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import api
+import utils
+import clientinfo
+import downloadutils
+import itemtypes
+import embydb_functions as embydb
+import kodidb_functions as kodidb
+import read_embyserver as embyserver
+import userclient
+import videonodes
+
+##################################################################################################
+
+
+class LibrarySync(threading.Thread):
+
+    _shared_state = {}
+
+    stop_thread = False
+    suspend_thread = False
+
+    # Track websocketclient updates
+    addedItems = []
+    updateItems = []
+    userdataItems = []
+    removeItems = []
+    forceLibraryUpdate = False
+    refresh_views = False
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.monitor = xbmc.Monitor()
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+        self.user = userclient.UserClient()
+        self.emby = embyserver.Read_EmbyServer()
+        self.vnodes = videonodes.VideoNodes()
+
+        threading.Thread.__init__(self)
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def progressDialog(self, title, forced=False):
+
+        dialog = None
+
+        if utils.settings('dbSyncIndicator') == "true" or forced:
+            dialog = xbmcgui.DialogProgressBG()
+            dialog.create("Emby for Kodi", title)
+            self.logMsg("Show progress dialog: %s" % title, 2)
+
+        return dialog
+
+    def startSync(self):
+        # Run at start up - optional to use the server plugin
+        if utils.settings('SyncInstallRunDone') == "true":
+            
+            # Validate views
+            self.refreshViews()
+            completed = False
+            # Verify if server plugin is installed.
+            if utils.settings('serverSync') == "true":
+                # Try to use fast start up
+                url = "{server}/emby/Plugins?format=json"
+                result = self.doUtils.downloadUrl(url)
+
+                for plugin in result:
+                    if plugin['Name'] == "Emby.Kodi Sync Queue":
+                        self.logMsg("Found server plugin.", 2)
+                        completed = self.fastSync()
+            
+            if not completed:
+                # Fast sync failed or server plugin is not found
+                completed = self.fullSync(manualrun=True)
+        else:
+            # Install sync is not completed
+            completed = self.fullSync()
+        
+        return completed
+
+    def fastSync(self):
+
+        lastSync = utils.settings('LastIncrementalSync')
+        if not lastSync:
+            lastSync = "2010-01-01T00:00:00Z"
+        self.logMsg("Last sync run: %s" % lastSync, 1)
+
+        url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json"
+        params = {'LastUpdateDT': lastSync}
+        result = self.doUtils.downloadUrl(url, parameters=params)
+
+        try:
+            processlist = {
+                
+                'added': result['ItemsAdded'],
+                'update': result['ItemsUpdated'],
+                'userdata': result['UserDataChanged'],
+                'remove': result['ItemsRemoved']
+            }
+            
+        except (KeyError, TypeError):
+            self.logMsg("Failed to retrieve latest updates using fast sync.", 1)
+            return False
+        
+        else:
+            self.logMsg("Fast sync changes: %s" % result, 1)
+            for action in processlist:
+                self.triage_items(action, processlist[action])
+
+            return True
+
+    def saveLastSync(self):
+        # Save last sync time
+        overlap = 2
+
+        url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json"
+        result = self.doUtils.downloadUrl(url)
+        try: # datetime fails when used more than once, TypeError
+            server_time = result['ServerDateTime']
+            server_time = datetime.strptime(server_time, "%Y-%m-%dT%H:%M:%SZ")
+        
+        except Exception as e:
+            # If the server plugin is not installed or an error happened.
+            self.logMsg("An exception occurred: %s" % e, 1)
+            time_now = datetime.utcnow()-timedelta(minutes=overlap)
+            lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
+            self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
+
+        else:
+            lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
+            self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
+
+        finally:
+            utils.settings('LastIncrementalSync', value=lastSync)
+
+    def shouldStop(self):
+        # Checkpoint during the syncing process
+        if self.monitor.abortRequested():
+            return True
+        elif utils.window('emby_shouldStop') == "true":
+            return True
+        else: # Keep going
+            return False
+
+    def dbCommit(self, connection):
+        # Central commit, verifies if Kodi database update is running
+        kodidb_scan = utils.window('emby_kodiScan') == "true"
+
+        while kodidb_scan:
+
+            self.logMsg("Kodi scan is running. Waiting...", 1)
+            kodidb_scan = utils.window('emby_kodiScan') == "true"
+
+            if self.shouldStop():
+                self.logMsg("Commit unsuccessful. Sync terminated.", 1)
+                break
+
+            if self.monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                self.logMsg("Commit unsuccessful.", 1)
+                break
+        else:
+            connection.commit()
+            self.logMsg("Commit successful.", 1)
+
+    def fullSync(self, manualrun=False, repair=False):
+        # Only run once when first setting up. Can be run manually.
+        emby = self.emby
+        music_enabled = utils.settings('enableMusic') == "true"
+
+        utils.window('emby_dbScan', value="true")
+        # Add sources
+        utils.sourcesXML()
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        # Create the tables for the emby database
+        # emby, view, version
+        embycursor.execute(
+            """CREATE TABLE IF NOT EXISTS emby(
+            emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, kodi_id INTEGER, 
+            kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, checksum INTEGER)""")
+        embycursor.execute(
+            """CREATE TABLE IF NOT EXISTS view(
+            view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
+        embycursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
+        embyconn.commit()
+        
+        # content sync: movies, tvshows, musicvideos, music
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+
+        if manualrun:
+            message = "Manual sync"
+        elif repair:
+            message = "Repair sync"
+        else:
+            message = "Initial sync"
+        
+        pDialog = self.progressDialog("%s" % message, forced=True)
+        starttotal = datetime.now()
+
+        # Set views
+        self.maintainViews(embycursor, kodicursor)
+        embyconn.commit()
+        
+        # Sync video library
+        process = {
+
+            'movies': self.movies,
+            'musicvideos': self.musicvideos,
+            'tvshows': self.tvshows,
+            'homevideos': self.homevideos
+        }
+        for itemtype in process:
+            startTime = datetime.now()
+            completed = process[itemtype](embycursor, kodicursor, pDialog, compare=manualrun)
+            if not completed:
+                
+                utils.window('emby_dbScan', clear=True)
+                if pDialog:
+                    pDialog.close()
+
+                embycursor.close()
+                kodicursor.close()
+                return False
+            else:
+                self.dbCommit(kodiconn)
+                embyconn.commit()
+                elapsedTime = datetime.now() - startTime
+                self.logMsg(
+                    "SyncDatabase (finished %s in: %s)"
+                    % (itemtype, str(elapsedTime).split('.')[0]), 1)
+
+        # sync music
+        if music_enabled:
+            
+            musicconn = utils.kodiSQL('music')
+            musiccursor = musicconn.cursor()
+            
+            startTime = datetime.now()
+            completed = self.music(embycursor, musiccursor, pDialog, compare=manualrun)
+            if not completed:
+
+                utils.window('emby_dbScan', clear=True)
+                if pDialog:
+                    pDialog.close()
+
+                embycursor.close()
+                musiccursor.close()
+                return False
+            else:
+                musicconn.commit()
+                embyconn.commit()
+                elapsedTime = datetime.now() - startTime
+                self.logMsg(
+                    "SyncDatabase (finished music in: %s)"
+                    % (str(elapsedTime).split('.')[0]), 1)
+            musiccursor.close()
+
+        if pDialog:
+            pDialog.close()
+        
+        embycursor.close()
+        kodicursor.close()
+        
+        utils.settings('SyncInstallRunDone', value="true")
+        utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
+        self.saveLastSync()
+        # tell any widgets to refresh because the content has changed
+        utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+        xbmc.executebuiltin('UpdateLibrary(video)')
+        elapsedtotal = datetime.now() - starttotal
+
+        utils.window('emby_dbScan', clear=True)
+        xbmcgui.Dialog().notification(
+                        heading="Emby for Kodi",
+                        message="%s completed in: %s!" % 
+                                (message, str(elapsedtotal).split('.')[0]),
+                        icon="special://home/addons/plugin.video.emby/icon.png",
+                        sound=False)
+        return True
+
+
+    def refreshViews(self):
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+
+        # Compare views, assign correct tags to items
+        self.maintainViews(embycursor, kodicursor)
+        
+        self.dbCommit(kodiconn)
+        kodicursor.close()
+
+        embyconn.commit()
+        embycursor.close()
+
+    def maintainViews(self, embycursor, kodicursor):
+        # Compare the views to emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        kodi_db = kodidb.Kodidb_Functions(kodicursor)
+        doUtils = self.doUtils
+        vnodes = self.vnodes
+        
+        # Get views
+        url = "{server}/emby/Users/{UserId}/Views?format=json"
+        result = doUtils.downloadUrl(url)
+        grouped_views = result['Items']
+
+        try:
+            groupedFolders = self.user.userSettings['Configuration']['GroupedFolders']
+        except TypeError:
+            url = "{server}/emby/Users/{UserId}?format=json"
+            result = doUtils.downloadUrl(url)
+            groupedFolders = result['Configuration']['GroupedFolders']
+
+        # total nodes for window properties
+        vnodes.clearProperties()
+        totalnodes = 0
+
+        # Set views for supported media type
+        mediatypes = ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music']
+        for mediatype in mediatypes:
+
+            # Get media folders from server
+            folders = self.emby.getViews(mediatype, root=True)
+            for folder in folders:
+
+                folderid = folder['id']
+                foldername = folder['name']
+                viewtype = folder['type']
+                
+                if folderid in groupedFolders:
+                    # Media folders are grouped into userview
+                    for grouped_view in grouped_views:
+                        if (grouped_view['Type'] == "UserView" and 
+                            grouped_view['CollectionType'] == mediatype):
+                            # Take the name of the userview
+                            foldername = grouped_view['Name']
+                            break
+
+                # Get current media folders from emby database
+                view = emby_db.getView_byId(folderid)
+                try:
+                    current_viewname = view[0]
+                    current_viewtype = view[1]
+                    current_tagid = view[2]
+
+                except TypeError:
+                    self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
+                    tagid = kodi_db.createTag(foldername)
+                    # Create playlist for the video library
+                    if mediatype != "music":
+                        utils.playlistXSP(mediatype, foldername, viewtype)
+                        # Create the video node
+                        if mediatype != "musicvideos":
+                            vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+                            totalnodes += 1
+                    # Add view to emby database
+                    emby_db.addView(folderid, foldername, viewtype, tagid)
+
+                else:
+                    self.logMsg(' '.join((
+
+                        "Found viewid: %s" % folderid,
+                        "viewname: %s" % current_viewname,
+                        "viewtype: %s" % current_viewtype,
+                        "tagid: %s" % current_tagid)), 2)
+
+                    # View was modified, update with latest info
+                    if current_viewname != foldername:
+                        self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1)
+                        tagid = kodi_db.createTag(foldername)
+                        
+                        # Update view with new info
+                        emby_db.updateView(foldername, tagid, folderid)
+
+                        if mediatype != "music":
+                            if emby_db.getView_byName(current_viewname) is None:
+                                # The tag could be a combined view. Ensure there's no other tags
+                                # with the same name before deleting playlist.
+                                utils.playlistXSP(
+                                    mediatype, current_viewname, current_viewtype, True)
+                                # Delete video node
+                                if mediatype != "musicvideos":
+                                    vnodes.viewNode(
+                                        indexnumber=totalnodes,
+                                        tagname=current_viewname,
+                                        mediatype=mediatype,
+                                        viewtype=current_viewtype,
+                                        delete=True)
+                            # Added new playlist
+                            utils.playlistXSP(mediatype, foldername, viewtype)
+                            # Add new video node
+                            if mediatype != "musicvideos":
+                                vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+                                totalnodes += 1
+                        
+                        # Update items with new tag
+                        items = emby_db.getItem_byView(folderid)
+                        for item in items:
+                            # Remove the "s" from viewtype for tags
+                            kodi_db.updateTag(
+                                current_tagid, tagid, item[0], current_viewtype[:-1])
+                    else:
+                        if mediatype != "music":
+                            # Validate the playlist exists or recreate it
+                            utils.playlistXSP(mediatype, foldername, viewtype)
+                            # Create the video node if not already exists
+                            if mediatype != "musicvideos":
+                                vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+                                totalnodes += 1
+        else:
+            # Add video nodes listings
+            vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites")
+            totalnodes += 1
+            vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites")
+            totalnodes += 1
+            vnodes.singleNode(totalnodes, "channels", "movies", "channels")
+            totalnodes += 1
+            # Save total
+            utils.window('Emby.nodes.total', str(totalnodes))
+
+
+    def movies(self, embycursor, kodicursor, pdialog, compare=False):
+        # Get movies from emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        movies = itemtypes.Movies(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('movies')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        if compare:
+            # Pull the list of movies and boxsets in Kodi
+            try:
+                all_kodimovies = dict(emby_db.getChecksum('Movie'))
+            except ValueError:
+                all_kodimovies = {}
+
+            try:
+                all_kodisets = dict(emby_db.getChecksum('BoxSet'))
+            except ValueError:
+                all_kodisets = {}
+
+            all_embymoviesIds = set()
+            all_embyboxsetsIds = set()
+            updatelist = []
+
+        ##### PROCESS MOVIES #####
+        for view in views:
+            
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="Gathering movies from view: %s..." % viewName)
+
+            if compare:
+                # Manual sync
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="Comparing movies from view: %s..." % viewName)
+
+                all_embymovies = emby.getMovies(viewId, basic=True)
+                for embymovie in all_embymovies['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embymovie)
+                    itemid = embymovie['Id']
+                    all_embymoviesIds.add(itemid)
+
+                    
+                    if all_kodimovies.get(itemid) != API.getChecksum():
+                        # Only update if movie is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+
+                self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1)
+                embymovies = emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+            else:
+                # Initial or repair sync
+                all_embymovies = emby.getMovies(viewId)
+                total = all_embymovies['TotalRecordCount']
+                embymovies = all_embymovies['Items']
+
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embymovie in embymovies:
+                # Process individual movies
+                if self.shouldStop():
+                    return False
+                
+                title = embymovie['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                movies.add_update(embymovie, viewName, viewId)
+        else:
+            self.logMsg("Movies finished.", 2)
+
+
+        ##### PROCESS BOXSETS #####
+        if pdialog:
+            pdialog.update(heading="Emby for Kodi", message="Gathering boxsets from server...")
+        
+        boxsets = emby.getBoxset()
+
+        if compare:
+            # Manual sync
+            embyboxsets = []
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="Comparing boxsets...")
+
+            for boxset in boxsets['Items']:
+
+                if self.shouldStop():
+                    return False
+
+                # Boxset has no real userdata, so using etag to compare
+                checksum = boxset['Etag']
+                itemid = boxset['Id']
+                all_embyboxsetsIds.add(itemid)
+
+                if all_kodisets.get(itemid) != checksum:
+                    # Only update if boxset is not in Kodi or checksum is different
+                    updatelist.append(itemid)
+                    embyboxsets.append(boxset)
+
+            self.logMsg("Boxsets to update: %s" % updatelist, 1)
+            total = len(updatelist)
+        else:
+            total = boxsets['TotalRecordCount']
+            embyboxsets = boxsets['Items']
+            
+
+        if pdialog:
+            pdialog.update(heading="Processing Boxsets / %s items" % total)
+
+        count = 0
+        for boxset in embyboxsets:
+            # Process individual boxset
+            if self.shouldStop():
+                return False
+
+            title = boxset['Name']
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            movies.add_updateBoxset(boxset)
+        else:
+            self.logMsg("Boxsets finished.", 2)
+
+
+        ##### PROCESS DELETES #####
+        if compare:
+            # Manual sync, process deletes
+            for kodimovie in all_kodimovies:
+                if kodimovie not in all_embymoviesIds:
+                    movies.remove(kodimovie)
+            else:
+                self.logMsg("Movies compare finished.", 1)
+
+            for boxset in all_kodisets:
+                if boxset not in all_embyboxsetsIds:
+                    movies.remove(boxset)
+            else:
+                self.logMsg("Boxsets compare finished.", 1)
+
+        return True
+
+    def musicvideos(self, embycursor, kodicursor, pdialog, compare=False):
+        # Get musicvideos from emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('musicvideos')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        if compare:
+            # Pull the list of musicvideos in Kodi
+            try:
+                all_kodimvideos = dict(emby_db.getChecksum('MusicVideo'))
+            except ValueError:
+                all_kodimvideos = {}
+
+            all_embymvideosIds = set()
+            updatelist = []
+
+        for view in views:
+            
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="Gathering musicvideos from view: %s..." % viewName)
+
+            if compare:
+                # Manual sync
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="Comparing musicvideos from view: %s..." % viewName)
+
+                all_embymvideos = emby.getMusicVideos(viewId, basic=True)
+                for embymvideo in all_embymvideos['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embymvideo)
+                    itemid = embymvideo['Id']
+                    all_embymvideosIds.add(itemid)
+
+                    
+                    if all_kodimvideos.get(itemid) != API.getChecksum():
+                        # Only update if musicvideo is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+
+                self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
+                embymvideos = emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+            else:
+                # Initial or repair sync
+                all_embymvideos = emby.getMusicVideos(viewId)
+                total = all_embymvideos['TotalRecordCount']
+                embymvideos = all_embymvideos['Items']
+
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embymvideo in embymvideos:
+                # Process individual musicvideo
+                if self.shouldStop():
+                    return False
+                
+                title = embymvideo['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                mvideos.add_update(embymvideo, viewName, viewId)
+        else:
+            self.logMsg("MusicVideos finished.", 2)
+        
+        ##### PROCESS DELETES #####
+        if compare:
+            # Manual sync, process deletes
+            for kodimvideo in all_kodimvideos:
+                if kodimvideo not in all_embymvideosIds:
+                    mvideos.remove(kodimvideo)
+            else:
+                self.logMsg("MusicVideos compare finished.", 1)
+
+        return True
+
+    def homevideos(self, embycursor, kodicursor, pdialog, compare=False):
+        # Get homevideos from emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        hvideos = itemtypes.HomeVideos(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('homevideos')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        if compare:
+            # Pull the list of homevideos in Kodi
+            try:
+                all_kodihvideos = dict(emby_db.getChecksum('Video'))
+            except ValueError:
+                all_kodihvideos = {}
+
+            all_embyhvideosIds = set()
+            updatelist = []
+
+        for view in views:
+            
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="Gathering homevideos from view: %s..." % viewName)
+            
+            all_embyhvideos = emby.getHomeVideos(viewId)
+
+            if compare:
+                # Manual sync
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="Comparing homevideos from view: %s..." % viewName)
+
+                for embyhvideo in all_embyhvideos['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embyhvideo)
+                    itemid = embyhvideo['Id']
+                    all_embyhvideosIds.add(itemid)
+
+                    
+                    if all_kodihvideos.get(itemid) != API.getChecksum():
+                        # Only update if homemovie is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+
+                self.logMsg("HomeVideos to update for %s: %s" % (viewName, updatelist), 1)
+                embyhvideos = emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+            else:
+                total = all_embyhvideos['TotalRecordCount']
+                embyhvideos = all_embyhvideos['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embyhvideo in embyhvideos:
+                # Process individual homemovies
+                if self.shouldStop():
+                    return False
+                
+                title = embyhvideo['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                hvideos.add_update(embyhvideo, viewName, viewId)
+        else:
+            self.logMsg("HomeVideos finished.", 2)
+
+        ##### PROCESS DELETES #####
+        if compare:
+            # Manual sync, process deletes
+            for kodihvideo in all_kodihvideos:
+                if kodihvideo not in all_embyhvideosIds:
+                    hvideos.remove(kodihvideo)
+            else:
+                self.logMsg("HomeVideos compare finished.", 1)
+        
+        return True
+
+    def tvshows(self, embycursor, kodicursor, pdialog, compare=False):
+        # Get shows from emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        tvshows = itemtypes.TVShows(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('tvshows')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        if compare:
+            # Pull the list of movies and boxsets in Kodi
+            try:
+                all_koditvshows = dict(emby_db.getChecksum('Series'))
+            except ValueError:
+                all_koditvshows = {}
+
+            try:
+                all_kodiepisodes = dict(emby_db.getChecksum('Episode'))
+            except ValueError:
+                all_kodiepisodes = {}
+
+            all_embytvshowsIds = set()
+            all_embyepisodesIds = set()
+            updatelist = []
+
+
+        for view in views:
+            
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="Gathering tvshows from view: %s..." % viewName)
+
+            if compare:
+                # Manual sync
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="Comparing tvshows from view: %s..." % viewName)
+
+                all_embytvshows = emby.getShows(viewId, basic=True)
+                for embytvshow in all_embytvshows['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embytvshow)
+                    itemid = embytvshow['Id']
+                    all_embytvshowsIds.add(itemid)
+
+                    
+                    if all_koditvshows.get(itemid) != API.getChecksum():
+                        # Only update if movie is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+
+                self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1)
+                embytvshows = emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+            else:
+                all_embytvshows = emby.getShows(viewId)
+                total = all_embytvshows['TotalRecordCount']
+                embytvshows = all_embytvshows['Items']
+
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embytvshow in embytvshows:
+                # Process individual show
+                if self.shouldStop():
+                    return False
+                
+                itemid = embytvshow['Id']
+                title = embytvshow['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                tvshows.add_update(embytvshow, viewName, viewId)
+
+                if not compare:
+                    # Process episodes
+                    all_episodes = emby.getEpisodesbyShow(itemid)
+                    for episode in all_episodes['Items']:
+
+                        # Process individual show
+                        if self.shouldStop():
+                            return False
+
+                        episodetitle = episode['Name']
+                        if pdialog:
+                            pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
+                        tvshows.add_updateEpisode(episode)
+            else:
+                if compare:
+                    # Get all episodes in view
+                    if pdialog:
+                        pdialog.update(
+                                heading="Emby for Kodi",
+                                message="Comparing episodes from view: %s..." % viewName)
+
+                    all_embyepisodes = emby.getEpisodes(viewId, basic=True)
+                    for embyepisode in all_embyepisodes['Items']:
+
+                        if self.shouldStop():
+                            return False
+
+                        API = api.API(embyepisode)
+                        itemid = embyepisode['Id']
+                        all_embyepisodesIds.add(itemid)
+
+                        if all_kodiepisodes.get(itemid) != API.getChecksum():
+                            # Only update if movie is not in Kodi or checksum is different
+                            updatelist.append(itemid)
+
+                    self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1)
+                    embyepisodes = emby.getFullItems(updatelist)
+                    total = len(updatelist)
+                    del updatelist[:]
+
+                    for episode in embyepisodes:
+
+                        # Process individual episode
+                        if self.shouldStop():
+                            return False
+
+                        title = episode['SeriesName']
+                        episodetitle = episode['Name']
+                        if pdialog:
+                            pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
+                        tvshows.add_updateEpisode(episode)
+        else:
+            self.logMsg("TVShows finished.", 2)
+        
+        ##### PROCESS DELETES #####
+        if compare:
+            # Manual sync, process deletes
+            for koditvshow in all_koditvshows:
+                if koditvshow not in all_embytvshowsIds:
+                    tvshows.remove(koditvshow)
+            else:
+                self.logMsg("TVShows compare finished.", 1)
+
+            for kodiepisode in all_kodiepisodes:
+                if kodiepisode not in all_embyepisodesIds:
+                    tvshows.remove(kodiepisode)
+            else:
+                self.logMsg("Episodes compare finished.", 1)
+
+        return True
+
+    def music(self, embycursor, kodicursor, pdialog, compare=False):
+        # Get music from emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        music = itemtypes.Music(embycursor, kodicursor)
+
+        if compare:
+            # Pull the list of movies and boxsets in Kodi
+            try:
+                all_kodiartists = dict(emby_db.getChecksum('MusicArtist'))
+            except ValueError:
+                all_kodiartists = {}
+
+            try:
+                all_kodialbums = dict(emby_db.getChecksum('MusicAlbum'))
+            except ValueError:
+                all_kodialbums = {}
+
+            try:
+                all_kodisongs = dict(emby_db.getChecksum('Audio'))
+            except ValueError:
+                all_kodisongs = {}
+
+            all_embyartistsIds = set()
+            all_embyalbumsIds = set()
+            all_embysongsIds = set()
+            updatelist = []
+
+        process = {
+
+            'artists': [emby.getArtists, music.add_updateArtist],
+            'albums': [emby.getAlbums, music.add_updateAlbum],
+            'songs': [emby.getSongs, music.add_updateSong]
+        }
+        types = ['artists', 'albums', 'songs']
+        for type in types:
+
+            if pdialog:
+                pdialog.update(
+                    heading="Emby for Kodi",
+                    message="Gathering %s..." % type)
+
+            if compare:
+                # Manual Sync
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="Comparing %s..." % type)
+
+                if type != "artists":
+                    all_embyitems = process[type][0](basic=True)
+                else:
+                    all_embyitems = process[type][0]()
+                for embyitem in all_embyitems['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embyitem)
+                    itemid = embyitem['Id']
+                    if type == "artists":
+                        all_embyartistsIds.add(itemid)
+                        if all_kodiartists.get(itemid) != API.getChecksum():
+                            # Only update if artist is not in Kodi or checksum is different
+                            updatelist.append(itemid)
+                    elif type == "albums":
+                        all_embyalbumsIds.add(itemid)
+                        if all_kodialbums.get(itemid) != API.getChecksum():
+                            # Only update if album is not in Kodi or checksum is different
+                            updatelist.append(itemid)
+                    else:
+                        all_embysongsIds.add(itemid)
+                        if all_kodisongs.get(itemid) != API.getChecksum():
+                            # Only update if songs is not in Kodi or checksum is different
+                            updatelist.append(itemid)
+
+                self.logMsg("%s to update: %s" % (type, updatelist), 1)
+                embyitems = emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+            else:
+                all_embyitems = process[type][0]()
+                total = all_embyitems['TotalRecordCount']
+                embyitems = all_embyitems['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (type, total))
+
+            count = 0
+            for embyitem in embyitems:
+                # Process individual item
+                if self.shouldStop():
+                    return False
+                
+                title = embyitem['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+
+                process[type][1](embyitem)
+            else:
+                self.logMsg("%s finished." % type, 2)
+
+        ##### PROCESS DELETES #####
+        if compare:
+            # Manual sync, process deletes
+            for kodiartist in all_kodiartists:
+                if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None:
+                    music.remove(kodiartist)
+            else:
+                self.logMsg("Artist compare finished.", 1)
+
+            for kodialbum in all_kodialbums:
+                if kodialbum not in all_embyalbumsIds:
+                    music.remove(kodialbum)
+            else:
+                self.logMsg("Albums compare finished.", 1)
+
+            for kodisong in all_kodisongs:
+                if kodisong not in all_embysongsIds:
+                    music.remove(kodisong)
+            else:
+                self.logMsg("Songs compare finished.", 1)
+
+        return True
+
+    # Reserved for websocket_client.py and fast start
+    def triage_items(self, process, items):
+
+        processlist = {
+
+            'added': self.addedItems,
+            'update': self.updateItems,
+            'userdata': self.userdataItems,
+            'remove': self.removeItems
+        }
+        if items:
+            if process == "userdata":
+                itemids = []
+                for item in items:
+                    itemids.append(item['ItemId'])
+                items = itemids
+
+            self.logMsg("Queue %s: %s" % (process, items), 1)
+            processlist[process].extend(items)
+
+    def incrementalSync(self):
+        
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        pDialog = None
+
+        if self.refresh_views:
+            # Received userconfig update
+            self.refresh_views = False
+            self.maintainViews(embycursor, kodicursor)
+            self.forceLibraryUpdate = True
+
+        if self.addedItems or self.updateItems or self.userdataItems or self.removeItems:
+            # Only present dialog if we are going to process items
+            pDialog = self.progressDialog('Incremental sync')
+
+
+        process = {
+
+            'added': self.addedItems,
+            'update': self.updateItems,
+            'userdata': self.userdataItems,
+            'remove': self.removeItems
+        }
+        types = ['added', 'update', 'userdata', 'remove']
+        for type in types:
+
+            if process[type] and utils.window('emby_kodiScan') != "true":
+                
+                listItems = list(process[type])
+                del process[type][:] # Reset class list
+
+                items_process = itemtypes.Items(embycursor, kodicursor)
+                update = False
+
+                # Prepare items according to process type
+                if type == "added":
+                    items = emby.sortby_mediatype(listItems)
+
+                elif type in ("userdata", "remove"):
+                    items = emby_db.sortby_mediaType(listItems, unsorted=False)
+                
+                else:
+                    items = emby_db.sortby_mediaType(listItems)
+                    if items.get('Unsorted'):
+                        sorted_items = emby.sortby_mediatype(items['Unsorted'])
+                        doupdate = items_process.itemsbyId(sorted_items, "added", pDialog)
+                        if doupdate:
+                            update = True
+                        del items['Unsorted']
+
+                doupdate = items_process.itemsbyId(items, type, pDialog)
+                if doupdate:
+                    update = True
+                    
+                if update:
+                    self.forceLibraryUpdate = True
+
+
+        if self.forceLibraryUpdate:
+            # Force update the Kodi library
+            self.forceLibraryUpdate = False
+            self.dbCommit(kodiconn)
+            embyconn.commit()
+            self.saveLastSync()
+
+            # tell any widgets to refresh because the content has changed
+            utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+
+            self.logMsg("Updating video library.", 1)
+            utils.window('emby_kodiScan', value="true")
+            xbmc.executebuiltin('UpdateLibrary(video)')
+
+        if pDialog:
+            pDialog.close()
+
+        kodicursor.close()
+        embycursor.close()
+
+
+    def compareDBVersion(self, current, minimum):
+        # It returns True is database is up to date. False otherwise.
+        self.logMsg("current: %s minimum: %s" % (current, minimum), 1)
+        currMajor, currMinor, currPatch = current.split(".")
+        minMajor, minMinor, minPatch = minimum.split(".")
+
+        if currMajor > minMajor:
+            return True
+        elif currMajor == minMajor and (currMinor > minMinor or
+                                       (currMinor == minMinor and currPatch >= minPatch)):
+            return True
+        else:
+            # Database out of date.
+            return False
+
+    def run(self):
+    
+        try:
+            self.run_internal()
+        except Exception as e:
+            xbmcgui.Dialog().ok(
+                        heading="Emby for Kodi",
+                        line1=(
+                            "Library sync thread has exited! "
+                            "You should restart Kodi now. "
+                            "Please report this on the forum."))
+            raise
+
+    def run_internal(self):
+
+        startupComplete = False
+        monitor = self.monitor
+
+        self.logMsg("---===### Starting LibrarySync ###===---", 0)
+
+        while not monitor.abortRequested():
+
+            # In the event the server goes offline
+            while self.suspend_thread:
+                # Set in service.py
+                if monitor.waitForAbort(5):
+                    # Abort was requested while waiting. We should exit
+                    break
+
+            if (utils.window('emby_dbCheck') != "true" and
+                    utils.settings('SyncInstallRunDone') == "true"):
+                
+                # Verify the validity of the database
+                currentVersion = utils.settings('dbCreatedWithVersion')
+                minVersion = utils.window('emby_minDBVersion')
+                uptoDate = self.compareDBVersion(currentVersion, minVersion)
+
+                if not uptoDate:
+                    self.logMsg(
+                        "Db version out of date: %s minimum version required: %s"
+                        % (currentVersion, minVersion), 0)
+                    
+                    resp = xbmcgui.Dialog().yesno(
+                                            heading="Db Version",
+                                            line1=(
+                                                "Detected the database needs to be "
+                                                "recreated for this version of Emby for Kodi. "
+                                                "Proceed?"))
+                    if not resp:
+                        self.logMsg("Db version out of date! USER IGNORED!", 0)
+                        xbmcgui.Dialog().ok(
+                                        heading="Emby for Kodi",
+                                        line1=(
+                                            "Emby for Kodi may not work correctly "
+                                            "until the database is reset."))
+                    else:
+                        utils.reset()
+
+                utils.window('emby_dbCheck', value="true")
+
+
+            if not startupComplete:
+                # Verify the video database can be found
+                videoDb = utils.getKodiVideoDBPath()
+                if not xbmcvfs.exists(videoDb):
+                    # Database does not exists
+                    self.logMsg(
+                            "The current Kodi version is incompatible "
+                            "with the Emby for Kodi add-on. Please visit "
+                            "https://github.com/MediaBrowser/Emby.Kodi/wiki "
+                            "to know which Kodi versions are supported.", 0)
+
+                    xbmcgui.Dialog().ok(
+                                    heading="Emby Warning",
+                                    line1=(
+                                        "Cancelling the database syncing process. "
+                                        "Current Kodi versoin: %s is unsupported. "
+                                        "Please verify your logs for more info."
+                                        % xbmc.getInfoLabel('System.BuildVersion')))
+                    break
+
+                # Run start up sync
+                self.logMsg("Db version: %s" % utils.settings('dbCreatedWithVersion'), 0)
+                self.logMsg("SyncDatabase (started)", 1)
+                startTime = datetime.now()
+                librarySync = self.startSync()
+                elapsedTime = datetime.now() - startTime
+                self.logMsg(
+                    "SyncDatabase (finished in: %s) %s"
+                    % (str(elapsedTime).split('.')[0], librarySync), 1)
+                # Only try the initial sync once per kodi session regardless
+                # This will prevent an infinite loop in case something goes wrong.
+                startupComplete = True
+
+            # Process updates
+            if utils.window('emby_dbScan') != "true":
+                self.incrementalSync()
+
+            if (utils.window('emby_onWake') == "true" and
+                    utils.window('emby_online') == "true"):
+                # Kodi is waking up
+                # Set in kodimonitor.py
+                utils.window('emby_onWake', clear=True)
+                if utils.window('emby_syncRunning') != "true":
+                    self.logMsg("SyncDatabase onWake (started)", 0)
+                    librarySync = self.startSync()
+                    self.logMsg("SyncDatabase onWake (finished) %s", librarySync, 0)
+
+            if self.stop_thread:
+                # Set in service.py
+                self.logMsg("Service terminated thread.", 2)
+                break
+
+            if monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                break
+
+        self.logMsg("###===--- LibrarySync Stopped ---===###", 0)
+
+    def stopThread(self):
+        self.stop_thread = True
+        self.logMsg("Ending thread...", 2)
+
+    def suspendThread(self):
+        self.suspend_thread = True
+        self.logMsg("Pausing thread...", 0)
+
+    def resumeThread(self):
+        self.suspend_thread = False
+        self.logMsg("Resuming thread...", 0)
\ No newline at end of file
diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py
new file mode 100644
index 00000000..affa2b81
--- /dev/null
+++ b/resources/lib/playbackutils.py
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import sys
+
+import xbmc
+import xbmcgui
+import xbmcplugin
+
+import api
+import artwork
+import clientinfo
+import downloadutils
+import playutils as putils
+import playlist
+import read_embyserver as embyserver
+import utils
+
+#################################################################################################
+
+
+class PlaybackUtils():
+    
+    
+    def __init__(self, item):
+
+        self.item = item
+        self.API = api.API(self.item)
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+
+        self.userid = utils.window('emby_currUser')
+        self.server = utils.window('emby_server%s' % self.userid)
+
+        self.artwork = artwork.Artwork()
+        self.emby = embyserver.Read_EmbyServer()
+        self.pl = playlist.Playlist()
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def play(self, itemid, dbid=None):
+
+        self.logMsg("Play called.", 1)
+
+        doUtils = self.doUtils
+        item = self.item
+        API = self.API
+        listitem = xbmcgui.ListItem()
+        playutils = putils.PlayUtils(item)
+
+        playurl = playutils.getPlayUrl()
+        if not playurl:
+            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
+
+        if dbid is None:
+            # Item is not in Kodi database
+            listitem.setPath(playurl)
+            self.setProperties(playurl, listitem)
+            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+        ############### ORGANIZE CURRENT PLAYLIST ################
+        
+        homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
+        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+        startPos = max(playlist.getposition(), 0) # Can return -1
+        sizePlaylist = playlist.size()
+        currentPosition = startPos
+
+        propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
+        introsPlaylist = False
+        dummyPlaylist = False
+
+        self.logMsg("Playlist start position: %s" % startPos, 1)
+        self.logMsg("Playlist plugin position: %s" % currentPosition, 1)
+        self.logMsg("Playlist size: %s" % sizePlaylist, 1)
+
+        ############### RESUME POINT ################
+        
+        userdata = API.getUserData()
+        seektime = API.adjustResume(userdata['Resume'])
+
+        # We need to ensure we add the intro and additional parts only once.
+        # Otherwise we get a loop.
+        if not propertiesPlayback:
+
+            utils.window('emby_playbackProps', value="true", windowid=10101)
+            self.logMsg("Setting up properties in playlist.", 1)
+
+            if (not homeScreen and not seektime and 
+                    utils.window('emby_customPlaylist', windowid=10101) != "true"):
+                
+                self.logMsg("Adding dummy file to playlist.", 2)
+                dummyPlaylist = True
+                playlist.add(playurl, listitem, index=startPos)
+                # Remove the original item from playlist 
+                self.pl.removefromPlaylist(startPos+1)
+                # Readd the original item to playlist - via jsonrpc so we have full metadata
+                self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
+                currentPosition += 1
+            
+            ############### -- CHECK FOR INTROS ################
+
+            if utils.settings('enableCinema') == "true" and not seektime:
+                # if we have any play them when the movie/show is not being resumed
+                url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid    
+                intros = doUtils.downloadUrl(url)
+
+                if intros['TotalRecordCount'] != 0:
+                    getTrailers = True
+
+                    if utils.settings('askCinema') == "true":
+                        resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?")
+                        if not resp:
+                            # User selected to not play trailers
+                            getTrailers = False
+                            self.logMsg("Skip trailers.", 1)
+                    
+                    if getTrailers:
+                        for intro in intros['Items']:
+                            # The server randomly returns intros, process them.
+                            introListItem = xbmcgui.ListItem()
+                            introPlayurl = putils.PlayUtils(intro).getPlayUrl()
+                            self.logMsg("Adding Intro: %s" % introPlayurl, 1)
+
+                            # Set listitem and properties for intros
+                            pbutils = PlaybackUtils(intro)
+                            pbutils.setProperties(introPlayurl, introListItem)
+
+                            self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
+                            introsPlaylist = True
+                            currentPosition += 1
+
+
+            ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
+
+            if homeScreen and not sizePlaylist:
+                # Extend our current playlist with the actual item to play
+                # only if there's no playlist first
+                self.logMsg("Adding main item to playlist.", 1)
+                self.pl.addtoPlaylist(dbid, item['Type'].lower())
+
+            # Ensure that additional parts are played after the main item
+            currentPosition += 1
+
+            ############### -- CHECK FOR ADDITIONAL PARTS ################
+            
+            if item.get('PartCount'):
+                # Only add to the playlist after intros have played
+                partcount = item['PartCount']
+                url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
+                parts = doUtils.downloadUrl(url)
+                for part in parts['Items']:
+
+                    additionalListItem = xbmcgui.ListItem()
+                    additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
+                    self.logMsg("Adding additional part: %s" % partcount, 1)
+
+                    # Set listitem and properties for each additional parts
+                    pbutils = PlaybackUtils(part)
+                    pbutils.setProperties(additionalPlayurl, additionalListItem)
+                    pbutils.setArtwork(additionalListItem)
+
+                    playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
+                    self.pl.verifyPlaylist()
+                    currentPosition += 1
+
+            if dummyPlaylist:
+                # Added a dummy file to the playlist,
+                # because the first item is going to fail automatically.
+                self.logMsg("Processed as a playlist. First item is skipped.", 1)
+                return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
+                
+
+        # We just skipped adding properties. Reset flag for next time.
+        elif propertiesPlayback:
+            self.logMsg("Resetting properties playback flag.", 2)
+            utils.window('emby_playbackProps', clear=True, windowid=10101)
+
+        #self.pl.verifyPlaylist()
+        ########## SETUP MAIN ITEM ##########
+
+        # For transcoding only, ask for audio/subs pref
+        if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
+            playurl = playutils.audioSubsPref(playurl)
+            utils.window('emby_%s.playmethod' % playurl, value="Transcode")
+
+        listitem.setPath(playurl)
+        self.setProperties(playurl, listitem)
+
+        ############### PLAYBACK ################
+
+        if homeScreen and seektime:
+            self.logMsg("Play as a widget item.", 1)
+            self.setListItem(listitem)
+            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+        elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or
+            (homeScreen and not sizePlaylist)):
+            # Playlist was created just now, play it.
+            self.logMsg("Play playlist.", 1)
+            xbmc.Player().play(playlist, startpos=startPos)
+
+        else:
+            self.logMsg("Play as a regular item.", 1)
+            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+    def setProperties(self, playurl, listitem):
+        # Set all properties necessary for plugin path playback
+        item = self.item
+        itemid = item['Id']
+        itemtype = item['Type']
+
+        embyitem = "emby_%s" % playurl
+        utils.window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
+        utils.window('%s.type' % embyitem, value=itemtype)
+        utils.window('%s.itemid' % embyitem, value=itemid)
+
+        if itemtype == "Episode":
+            utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
+        else:
+            utils.window('%s.refreshid' % embyitem, value=itemid)
+
+        # Append external subtitles to stream
+        playmethod = utils.window('%s.playmethod' % embyitem)
+        # Only for direct play and direct stream
+        subtitles = self.externalSubs(playurl)
+        if playmethod in ("DirectStream", "Transcode"):
+            # Direct play automatically appends external
+            listitem.setSubtitles(subtitles)
+
+        self.setArtwork(listitem)
+
+    def externalSubs(self, playurl):
+
+        externalsubs = []
+        mapping = {}
+
+        item = self.item
+        itemid = item['Id']
+        try:
+            mediastreams = item['MediaSources'][0]['MediaStreams']
+        except (TypeError, KeyError, IndexError):
+            return
+
+        kodiindex = 0
+        for stream in mediastreams:
+
+            index = stream['Index']
+            # Since Emby returns all possible tracks together, have to pull only external subtitles.
+            # IsTextSubtitleStream if true, is available to download from emby.
+            if (stream['Type'] == "Subtitle" and 
+                    stream['IsExternal'] and stream['IsTextSubtitleStream']):
+
+                # Direct stream
+                url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
+                        % (self.server, itemid, itemid, index))
+                
+                # map external subtitles for mapping
+                mapping[kodiindex] = index
+                externalsubs.append(url)
+                kodiindex += 1
+        
+        mapping = json.dumps(mapping)
+        utils.window('emby_%s.indexMapping' % playurl, value=mapping)
+
+        return externalsubs
+
+    def setArtwork(self, listItem):
+        # Set up item and item info
+        item = self.item
+        artwork = self.artwork
+
+        allartwork = artwork.getAllArtwork(item, parentInfo=True)
+        # Set artwork for listitem
+        arttypes = {
+
+            'poster': "Primary",
+            'tvshow.poster': "Primary",
+            'clearart': "Art",
+            'tvshow.clearart': "Art",
+            'clearlogo': "Logo",
+            'tvshow.clearlogo': "Logo",
+            'discart': "Disc",
+            'fanart_image': "Backdrop",
+            'landscape': "Thumb"
+        }
+        for arttype in arttypes:
+
+            art = arttypes[arttype]
+            if art == "Backdrop":
+                try: # Backdrop is a list, grab the first backdrop
+                    self.setArtProp(listItem, arttype, allartwork[art][0])
+                except: pass
+            else:
+                self.setArtProp(listItem, arttype, allartwork[art])
+
+    def setArtProp(self, listItem, arttype, path):
+        
+        if arttype in (
+                'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
+                'medium_landscape', 'medium_poster', 'small_fanartimage',
+                'medium_fanartimage', 'fanart_noindicators'):
+            
+            listItem.setProperty(arttype, path)
+        else:
+            listItem.setArt({arttype: path})
+
+    def setListItem(self, listItem):
+
+        item = self.item
+        type = item['Type']
+        API = self.API
+        people = API.getPeople()
+        studios = API.getStudios()
+
+        metadata = {
+            
+            'title': item.get('Name', "Missing name"),
+            'year': item.get('ProductionYear'),
+            'plot': API.getOverview(),
+            'director': people.get('Director'),
+            'writer': people.get('Writer'),
+            'mpaa': API.getMpaa(),
+            'genre': " / ".join(item['Genres']),
+            'studio': " / ".join(studios),
+            'aired': API.getPremiereDate(),
+            'rating': item.get('CommunityRating'),
+            'votes': item.get('VoteCount')
+        }
+
+        if "Episode" in type:
+            # Only for tv shows
+            thumbId = item.get('SeriesId')
+            season = item.get('ParentIndexNumber', -1)
+            episode = item.get('IndexNumber', -1)
+            show = item.get('SeriesName', "")
+
+            metadata['TVShowTitle'] = show
+            metadata['season'] = season
+            metadata['episode'] = episode
+
+        listItem.setProperty('IsPlayable', 'true')
+        listItem.setProperty('IsFolder', 'false')
+        listItem.setLabel(metadata['title'])
+        listItem.setInfo('video', infoLabels=metadata)
\ No newline at end of file
diff --git a/resources/lib/player.py b/resources/lib/player.py
new file mode 100644
index 00000000..b6d25e19
--- /dev/null
+++ b/resources/lib/player.py
@@ -0,0 +1,502 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+
+import xbmc
+import xbmcgui
+
+import utils
+import clientinfo
+import downloadutils
+import kodidb_functions as kodidb
+import websocket_client as wsc
+
+#################################################################################################
+
+
+class Player(xbmc.Player):
+
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+
+    played_info = {}
+    playStats = {}
+    currentFile = None
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+        self.ws = wsc.WebSocket_Client()
+        self.xbmcplayer = xbmc.Player()
+
+        self.logMsg("Starting playback monitor.", 2)
+
+    def logMsg(self, msg, lvl=1):
+        
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def GetPlayStats(self):
+        return self.playStats
+
+    def onPlayBackStarted( self ):
+        # Will be called when xbmc starts playing a file
+        xbmcplayer = self.xbmcplayer
+        self.stopAll()
+
+        # Get current file
+        try:
+            currentFile = xbmcplayer.getPlayingFile()
+            xbmc.sleep(300)
+        except:
+            currentFile = ""
+            count = 0
+            while not currentFile:
+                xbmc.sleep(100)
+                try:
+                    currentFile = xbmcplayer.getPlayingFile()
+                except: pass
+
+                if count == 5: # try 5 times
+                    self.logMsg("Cancelling playback report...", 1)
+                    break
+                else: count += 1
+
+
+        if currentFile:
+
+            self.currentFile = currentFile
+            
+            # We may need to wait for info to be set in kodi monitor
+            itemId = utils.window("emby_%s.itemid" % currentFile)
+            tryCount = 0
+            while not itemId:
+                
+                xbmc.sleep(200)
+                itemId = utils.window("emby_%s.itemid" % currentFile)
+                if tryCount == 20: # try 20 times or about 10 seconds
+                    self.logMsg("Could not find itemId, cancelling playback report...", 1)
+                    break
+                else: tryCount += 1
+            
+            else:
+                self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
+
+                # Only proceed if an itemId was found.
+                embyitem = "emby_%s" % currentFile
+                runtime = utils.window("%s.runtime" % embyitem)
+                refresh_id = utils.window("%s.refreshid" % embyitem)
+                playMethod = utils.window("%s.playmethod" % embyitem)
+                itemType = utils.window("%s.type" % embyitem)
+                utils.window('emby_skipWatched%s' % itemId, value="true")
+
+                seekTime = xbmcplayer.getTime()
+
+                # Get playback volume
+                volume_query = {
+
+                    "jsonrpc": "2.0",
+                    "id": 1,
+                    "method": "Application.GetProperties",
+                    "params": {
+
+                        "properties": ["volume", "muted"] 
+                    }
+                }
+                result = xbmc.executeJSONRPC(json.dumps(volume_query))
+                result = json.loads(result)
+                result = result.get('result')
+                
+                volume = result.get('volume')
+                muted = result.get('muted')
+
+                # Postdata structure to send to Emby server
+                url = "{server}/emby/Sessions/Playing"
+                postdata = {
+
+                    'QueueableMediaTypes': "Video",
+                    'CanSeek': True,
+                    'ItemId': itemId,
+                    'MediaSourceId': itemId,
+                    'PlayMethod': playMethod,
+                    'VolumeLevel': volume,
+                    'PositionTicks': int(seekTime * 10000000),
+                    'IsMuted': muted
+                }
+
+                # Get the current audio track and subtitles
+                if playMethod == "Transcode":
+                    # property set in PlayUtils.py
+                    postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
+                    postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex"
+                                                                    % currentFile)
+                else:
+                    # Get the current kodi audio and subtitles and convert to Emby equivalent
+                    tracks_query = {
+
+                        "jsonrpc": "2.0",
+                        "id": 1,
+                        "method": "Player.GetProperties",
+                        "params": {
+
+                            "playerid": 1,
+                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+                        }
+                    }
+                    result = xbmc.executeJSONRPC(json.dumps(tracks_query))
+                    result = json.loads(result)
+                    result = result.get('result')
+
+                    try: # Audio tracks
+                        indexAudio = result['currentaudiostream']['index']
+                    except (KeyError, TypeError):
+                        indexAudio = 0
+                    
+                    try: # Subtitles tracks
+                        indexSubs = result['currentsubtitle']['index']
+                    except (KeyError, TypeError):
+                        indexSubs = 0
+
+                    try: # If subtitles are enabled
+                        subsEnabled = result['subtitleenabled']
+                    except (KeyError, TypeError):
+                        subsEnabled = ""
+
+                    # Postdata for the audio
+                    postdata['AudioStreamIndex'] = indexAudio + 1
+                    
+                    # Postdata for the subtitles
+                    if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
+                        
+                        # Number of audiotracks to help get Emby Index
+                        audioTracks = len(xbmc.Player().getAvailableAudioStreams())
+                        mapping = utils.window("%s.indexMapping" % embyitem)
+
+                        if mapping: # Set in playbackutils.py
+                            
+                            self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
+                            externalIndex = json.loads(mapping)
+
+                            if externalIndex.get(str(indexSubs)):
+                                # If the current subtitle is in the mapping
+                                postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
+                            else:
+                                # Internal subtitle currently selected
+                                subindex = indexSubs - len(externalIndex) + audioTracks + 1
+                                postdata['SubtitleStreamIndex'] = subindex
+                        
+                        else: # Direct paths enabled scenario or no external subtitles set
+                            postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
+                    else:
+                        postdata['SubtitleStreamIndex'] = ""
+                
+
+                # Post playback to server
+                self.logMsg("Sending POST play started: %s." % postdata, 2)
+                self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
+                
+                # Ensure we do have a runtime
+                try:
+                    runtime = int(runtime)
+                except ValueError:
+                    runtime = xbmcplayer.getTotalTime()
+                    self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
+
+                # Save data map for updates and position calls
+                data = {
+                    
+                    'runtime': runtime,
+                    'item_id': itemId,
+                    'refresh_id': refresh_id,
+                    'currentfile': currentFile,
+                    'AudioStreamIndex': postdata['AudioStreamIndex'],
+                    'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
+                    'playmethod': playMethod,
+                    'Type': itemType,
+                    'currentPosition': int(seekTime)
+                }
+                
+                self.played_info[currentFile] = data
+                self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
+
+                # log some playback stats
+                '''if(itemType != None):
+                    if(self.playStats.get(itemType) != None):
+                        count = self.playStats.get(itemType) + 1
+                        self.playStats[itemType] = count
+                    else:
+                        self.playStats[itemType] = 1
+                        
+                if(playMethod != None):
+                    if(self.playStats.get(playMethod) != None):
+                        count = self.playStats.get(playMethod) + 1
+                        self.playStats[playMethod] = count
+                    else:
+                        self.playStats[playMethod] = 1'''
+
+    def reportPlayback(self):
+        
+        self.logMsg("reportPlayback Called", 2)
+        xbmcplayer = self.xbmcplayer
+
+        # Get current file
+        currentFile = self.currentFile
+        data = self.played_info.get(currentFile)
+
+        # only report playback if emby has initiated the playback (item_id has value)
+        if data:
+            # Get playback information
+            itemId = data['item_id']
+            audioindex = data['AudioStreamIndex']
+            subtitleindex = data['SubtitleStreamIndex']
+            playTime = data['currentPosition']
+            playMethod = data['playmethod']
+            paused = data.get('paused', False)
+
+
+            # Get playback volume
+            volume_query = {
+
+                    "jsonrpc": "2.0",
+                    "id": 1,
+                    "method": "Application.GetProperties",
+                    "params": {
+
+                        "properties": ["volume", "muted"] 
+                    }
+                }
+            result = xbmc.executeJSONRPC(json.dumps(volume_query))
+            result = json.loads(result)
+            result = result.get('result')
+
+            volume = result.get('volume')
+            muted = result.get('muted')
+
+            # Postdata for the websocketclient report
+            postdata = {
+
+                'QueueableMediaTypes': "Video",
+                'CanSeek': True,
+                'ItemId': itemId,
+                'MediaSourceId': itemId,
+                'PlayMethod': playMethod,
+                'PositionTicks': int(playTime * 10000000),
+                'IsPaused': paused,
+                'VolumeLevel': volume,
+                'IsMuted': muted
+            }
+
+            if playMethod == "Transcode":
+                # Track can't be changed, keep reporting the same index
+                postdata['AudioStreamIndex'] = audioindex
+                postdata['AudioStreamIndex'] = subtitleindex
+
+            else:
+                # Get current audio and subtitles track
+                tracks_query = {
+
+                        "jsonrpc": "2.0",
+                        "id": 1,
+                        "method": "Player.GetProperties",
+                        "params": {
+
+                            "playerid": 1,
+                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+                        }
+                    }
+                result = xbmc.executeJSONRPC(json.dumps(tracks_query))
+                result = json.loads(result)
+                result = result.get('result')
+
+                try: # Audio tracks
+                    indexAudio = result['currentaudiostream']['index']
+                except (KeyError, TypeError):
+                    indexAudio = 0
+                
+                try: # Subtitles tracks
+                    indexSubs = result['currentsubtitle']['index']
+                except (KeyError, TypeError):
+                    indexSubs = 0
+
+                try: # If subtitles are enabled
+                    subsEnabled = result['subtitleenabled']
+                except (KeyError, TypeError):
+                    subsEnabled = ""
+
+                # Postdata for the audio
+                data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
+                
+                # Postdata for the subtitles
+                if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
+                    
+                    # Number of audiotracks to help get Emby Index
+                    audioTracks = len(xbmc.Player().getAvailableAudioStreams())
+                    mapping = utils.window("emby_%s.indexMapping" % currentFile)
+
+                    if mapping: # Set in PlaybackUtils.py
+                        
+                        self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
+                        externalIndex = json.loads(mapping)
+
+                        if externalIndex.get(str(indexSubs)):
+                            # If the current subtitle is in the mapping
+                            subindex = [externalIndex[str(indexSubs)]] * 2
+                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                        else:
+                            # Internal subtitle currently selected
+                            subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
+                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                    
+                    else: # Direct paths enabled scenario or no external subtitles set
+                        subindex = [indexSubs + audioTracks + 1] * 2
+                        data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                else:
+                    data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
+
+            # Report progress via websocketclient
+            postdata = json.dumps(postdata)
+            self.logMsg("Report: %s" % postdata, 2)
+            self.ws.sendProgressUpdate(postdata)
+
+    def onPlayBackPaused( self ):
+
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            self.played_info[currentFile]['paused'] = True
+        
+            self.reportPlayback()
+
+    def onPlayBackResumed( self ):
+
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            self.played_info[currentFile]['paused'] = False
+        
+            self.reportPlayback()
+
+    def onPlayBackSeek( self, time, seekOffset ):
+        # Make position when seeking a bit more accurate
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            position = self.xbmcplayer.getTime()
+            self.played_info[currentFile]['currentPosition'] = position
+
+            self.reportPlayback()
+    
+    def onPlayBackStopped( self ):
+        # Will be called when user stops xbmc playing a file
+        self.logMsg("ONPLAYBACK_STOPPED", 2)
+        xbmcgui.Window(10101).clearProperties()
+        self.logMsg("Clear playlist properties.")
+        self.stopAll()
+
+    def onPlayBackEnded( self ):
+        # Will be called when xbmc stops playing a file
+        self.logMsg("ONPLAYBACK_ENDED", 2)
+        self.stopAll()
+
+    def stopAll(self):
+
+        doUtils = self.doUtils
+
+        if not self.played_info:
+            return 
+            
+        self.logMsg("Played_information: %s" % self.played_info, 1)
+        # Process each items
+        for item in self.played_info:
+            
+            data = self.played_info.get(item)
+            if data:
+                
+                self.logMsg("Item path: %s" % item, 2)
+                self.logMsg("Item data: %s" % data, 2)
+
+                runtime = data['runtime']
+                currentPosition = data['currentPosition']
+                itemid = data['item_id']
+                refresh_id = data['refresh_id']
+                currentFile = data['currentfile']
+                type = data['Type']
+                playMethod = data['playmethod']
+
+                if currentPosition and runtime:
+                    try:
+                        percentComplete = (currentPosition * 10000000) / int(runtime)
+                    except ZeroDivisionError:
+                        # Runtime is 0.
+                        percentComplete = 0
+                        
+                    markPlayedAt = float(utils.settings('markPlayed')) / 100
+                    self.logMsg(
+                        "Percent complete: %s Mark played at: %s"
+                        % (percentComplete, markPlayedAt), 1)
+
+                    # Prevent manually mark as watched in Kodi monitor
+                    utils.window('emby_skipWatched%s' % itemid, value="true")
+
+                    self.stopPlayback(data)
+                    # Stop transcoding
+                    if playMethod == "Transcode":
+                        self.logMsg("Transcoding for %s terminated." % itemid, 1)
+                        deviceId = self.clientInfo.getDeviceId()
+                        url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
+                        doUtils.downloadUrl(url, type="DELETE")
+
+                    # Send the delete action to the server.
+                    offerDelete = False
+
+                    if type == "Episode" and utils.settings('deleteTV') == "true":
+                        offerDelete = True
+                    elif type == "Movie" and utils.settings('deleteMovies') == "true":
+                        offerDelete = True
+
+                    if utils.settings('offerDelete') != "true":
+                        # Delete could be disabled, even if the subsetting is enabled.
+                        offerDelete = False
+
+                    if percentComplete >= markPlayedAt and offerDelete:
+                        if utils.settings('skipConfirmDelete') != "true":
+                            resp = xbmcgui.Dialog().yesno(
+                                                    heading="Confirm delete",
+                                                    line1="Delete file on Emby Server?")
+                            if not resp:
+                                self.logMsg("User skipped deletion.", 1)
+                                continue
+
+                        url = "{server}/emby/Items/%s?format=json" % itemid
+                        self.logMsg("Deleting request: %s" % itemid)
+                        doUtils.downloadUrl(url, type="DELETE")
+    
+        self.played_info.clear()
+    
+    def stopPlayback(self, data):
+        
+        self.logMsg("stopPlayback called", 2)
+        
+        itemId = data['item_id']
+        currentPosition = data['currentPosition']
+        positionTicks = int(currentPosition * 10000000)
+
+        url = "{server}/emby/Sessions/Playing/Stopped"
+        postdata = {
+            
+            'ItemId': itemId,
+            'MediaSourceId': itemId,
+            'PositionTicks': positionTicks
+        }
+        self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
\ No newline at end of file
diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py
new file mode 100644
index 00000000..0a74690b
--- /dev/null
+++ b/resources/lib/playutils.py
@@ -0,0 +1,397 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import clientinfo
+import utils
+
+#################################################################################################
+
+
+class PlayUtils():
+    
+    
+    def __init__(self, item):
+
+        self.item = item
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+
+        self.userid = utils.window('emby_currUser')
+        self.server = utils.window('emby_server%s' % self.userid)
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+    
+
+    def getPlayUrl(self):
+
+        item = self.item
+        playurl = None
+
+        if item['MediaSources'][0]['Protocol'] == "Http":
+            # Only play as http
+            self.logMsg("File protocol is http.", 1)
+            playurl = self.httpPlay()
+            utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
+
+        elif self.isDirectPlay():
+
+            self.logMsg("File is direct playing.", 1)
+            playurl = self.directPlay()
+            playurl = playurl.encode('utf-8')
+            # Set playmethod property
+            utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
+
+        elif self.isDirectStream():
+            
+            self.logMsg("File is direct streaming.", 1)
+            playurl = self.directStream()
+            # Set playmethod property
+            utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
+
+        elif self.isTranscoding():
+            
+            self.logMsg("File is transcoding.", 1)
+            playurl = self.transcoding()
+            # Set playmethod property
+            utils.window('emby_%s.playmethod' % playurl, value="Transcode")
+
+        return playurl
+
+    def httpPlay(self):
+        # Audio, Video, Photo
+        item = self.item
+        server = self.server
+
+        itemid = item['Id']
+        mediatype = item['MediaType']
+
+        if type == "Audio":
+            playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
+        else:
+            playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
+
+        return playurl
+
+    def isDirectPlay(self):
+
+        item = self.item
+
+        # Requirement: Filesystem, Accessible path
+        if utils.settings('playFromStream') == "true":
+            # User forcing to play via HTTP
+            self.logMsg("Can't direct play, play from HTTP enabled.", 1)
+            return False
+
+        if (utils.settings('transcodeH265') == "true" and 
+                result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+            # Avoid H265 1080p
+            self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
+            return False
+
+        canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
+        # Make sure direct play is supported by the server
+        if not canDirectPlay:
+            self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
+            return False
+
+        location = item['LocationType']
+        if location == "FileSystem":
+            # Verify the path
+            if not self.fileExists():
+                self.logMsg("Unable to direct play.")
+                try:
+                    count = int(utils.settings('failCount'))
+                except ValueError:
+                    count = 0
+                self.logMsg("Direct play failed: %s times." % count, 1)
+
+                if count < 2:
+                    # Let the user know that direct play failed
+                    utils.settings('failCount', value=str(count+1))
+                    xbmcgui.Dialog().notification(
+                                        heading="Emby server",
+                                        message="Unable to direct play.",
+                                        icon="special://home/addons/plugin.video.emby/icon.png",
+                                        sound=False)
+                elif utils.settings('playFromStream') != "true":
+                    # Permanently set direct stream as true
+                    utils.settings('playFromStream', value="true")
+                    utils.settings('failCount', value="0")
+                    xbmcgui.Dialog().notification(
+                                        heading="Emby server",
+                                        message=("Direct play failed 3 times. Enabled play "
+                                                 "from HTTP in the add-on settings."),
+                                        icon="special://home/addons/plugin.video.emby/icon.png",
+                                        sound=False)
+                return False
+
+        return True
+
+    def directPlay(self):
+
+        item = self.item
+
+        try:
+            playurl = item['MediaSources'][0]['Path']
+        except (IndexError, KeyError):
+            playurl = item['Path']
+
+        if item.get('VideoType'):
+            # Specific format modification
+            type = item['VideoType']
+
+            if type == "Dvd":
+                playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
+            elif type == "Bluray":
+                playurl = "%s/BDMV/index.bdmv" % playurl
+
+        # Assign network protocol
+        if playurl.startswith('\\\\'):
+            playurl = playurl.replace("\\\\", "smb://")
+            playurl = playurl.replace("\\", "/")
+
+        if "apple.com" in playurl:
+            USER_AGENT = "QuickTime/7.7.4"
+            playurl += "?|User-Agent=%s" % USER_AGENT
+
+        return playurl
+
+    def fileExists(self):
+
+        if 'Path' not in self.item:
+            # File has no path defined in server
+            return False
+
+        # Convert path to direct play
+        path = self.directPlay()
+        self.logMsg("Verifying path: %s" % path, 1)
+
+        if xbmcvfs.exists(path):
+            self.logMsg("Path exists.", 1)
+            return True
+
+        elif ":" not in path:
+            self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
+            return True
+
+        else:
+            self.logMsg("Failed to find file.")
+            return False
+
+    def isDirectStream(self):
+
+        item = self.item
+
+        if (utils.settings('transcodeH265') == "true" and 
+                result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+            # Avoid H265 1080p
+            self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
+            return False
+
+        # Requirement: BitRate, supported encoding
+        canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
+        # Make sure the server supports it
+        if not canDirectStream:
+            return False
+
+        # Verify the bitrate
+        if not self.isNetworkSufficient():
+            self.logMsg("The network speed is insufficient to direct stream file.", 1)
+            return False
+
+        return True
+
+    def directStream(self):
+
+        item = self.item
+        server = self.server
+
+        itemid = item['Id']
+        type = item['Type']
+
+        if 'Path' in item and item['Path'].endswith('.strm'):
+            # Allow strm loading when direct streaming
+            playurl = self.directPlay()
+        elif type == "Audio":
+            playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
+        else:
+            playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
+
+        return playurl
+
+    def isNetworkSufficient(self):
+
+        settings = self.getBitrate()*1000
+
+        try:
+            sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
+        except (KeyError, TypeError):
+            self.logMsg("Bitrate value is missing.", 1)
+        else:
+            self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
+                        % (settings, sourceBitrate), 1)
+            if settings < sourceBitrate:
+                return False
+
+        return True
+
+    def isTranscoding(self):
+
+        item = self.item
+
+        canTranscode = item['MediaSources'][0]['SupportsTranscoding']
+        # Make sure the server supports it
+        if not canTranscode:
+            return False
+
+        return True
+
+    def transcoding(self):
+
+        item = self.item
+
+        if 'Path' in item and item['Path'].endswith('.strm'):
+            # Allow strm loading when transcoding
+            playurl = self.directPlay()
+        else:
+            itemid = item['Id']
+            deviceId = self.clientInfo.getDeviceId()
+            playurl = (
+                "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
+                % (self.server, itemid, itemid)
+            )
+            playurl = (
+                "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
+                % (playurl, deviceId, self.getBitrate()*1000))
+
+        return playurl
+
+    def getBitrate(self):
+
+        # get the addon video quality
+        videoQuality = utils.settings('videoBitrate')
+        bitrate = {
+
+            '0': 664,
+            '1': 996,
+            '2': 1320,
+            '3': 2000,
+            '4': 3200,
+            '5': 4700,
+            '6': 6200,
+            '7': 7700,
+            '8': 9200,
+            '9': 10700,
+            '10': 12200,
+            '11': 13700,
+            '12': 15200,
+            '13': 16700,
+            '14': 18200,
+            '15': 20000,
+            '16': 40000,
+            '17': 100000,
+            '18': 1000000
+        }
+
+        # max bit rate supported by server (max signed 32bit integer)
+        return bitrate.get(videoQuality, 2147483)
+
+    def audioSubsPref(self, url):
+        # For transcoding only
+        # Present the list of audio to select from
+        audioStreamsList = {}
+        audioStreams = []
+        audioStreamsChannelsList = {}
+        subtitleStreamsList = {}
+        subtitleStreams = ['No subtitles']
+        selectAudioIndex = ""
+        selectSubsIndex = ""
+        playurlprefs = "%s" % url
+
+        item = self.item
+        try:
+            mediasources = item['MediaSources'][0]
+            mediastreams = mediasources['MediaStreams']
+        except (TypeError, KeyError, IndexError):
+            return
+
+        for stream in mediastreams:
+            # Since Emby returns all possible tracks together, have to sort them.
+            index = stream['Index']
+            type = stream['Type']
+
+            if 'Audio' in type:
+                codec = stream['Codec']
+                channelLayout = stream.get('ChannelLayout', "")
+               
+                try:
+                    track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
+                except:
+                    track = "%s - %s %s" % (index, codec, channelLayout)
+                
+                audioStreamsChannelsList[index] = stream['Channels']
+                audioStreamsList[track] = index
+                audioStreams.append(track)
+
+            elif 'Subtitle' in type:
+                if stream['IsExternal']:
+                    continue
+                try:
+                    track = "%s - %s" % (index, stream['Language'])
+                except:
+                    track = "%s - %s" % (index, stream['Codec'])
+
+                default = stream['IsDefault']
+                forced = stream['IsForced']
+                if default:
+                    track = "%s - Default" % track
+                if forced:
+                    track = "%s - Forced" % track
+
+                subtitleStreamsList[track] = index
+                subtitleStreams.append(track)
+
+
+        if len(audioStreams) > 1:
+            resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
+            if resp > -1:
+                # User selected audio
+                selected = audioStreams[resp]
+                selectAudioIndex = audioStreamsList[selected]
+                playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
+            else: # User backed out of selection
+                playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
+        else: # There's only one audiotrack.
+            selectAudioIndex = audioStreamsList[audioStreams[0]]
+            playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
+
+        if len(subtitleStreams) > 1:
+            resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
+            if resp == 0:
+                # User selected no subtitles
+                pass
+            elif resp > -1:
+                # User selected subtitles
+                selected = subtitleStreams[resp]
+                selectSubsIndex = subtitleStreamsList[selected]
+                playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
+            else: # User backed out of selection
+                playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
+
+        # Get number of channels for selected audio track
+        audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
+        if audioChannels > 2:
+            playurlprefs += "&AudioBitrate=384000"
+        else:
+            playurlprefs += "&AudioBitrate=192000"
+
+        return playurlprefs
\ No newline at end of file
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
new file mode 100644
index 00000000..a6562a53
--- /dev/null
+++ b/resources/lib/userclient.py
@@ -0,0 +1,471 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import hashlib
+import threading
+
+import xbmc
+import xbmcgui
+import xbmcaddon
+import xbmcvfs
+
+import artwork
+import utils
+import clientinfo
+import downloadutils
+
+##################################################################################################
+
+
+class UserClient(threading.Thread):
+
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+
+    stopClient = False
+    auth = True
+    retry = 0
+
+    currUser = None
+    currUserId = None
+    currServer = None
+    currToken = None
+    HasAccess = True
+    AdditionalUser = []
+
+    userSettings = None
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.addon = xbmcaddon.Addon()
+
+        self.addonName = clientinfo.ClientInfo().getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+        self.logLevel = int(utils.settings('logLevel'))
+        
+        threading.Thread.__init__(self)
+
+    def logMsg(self, msg, lvl=1):
+        
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def getAdditionalUsers(self):
+
+        additionalUsers = utils.settings('additionalUsers')
+        
+        if additionalUsers:
+            self.AdditionalUser = additionalUsers.split(',')
+
+    def getUsername(self):
+
+        username = utils.settings('username')
+
+        if not username:
+            self.logMsg("No username saved.", 2)
+            return ""
+
+        return username
+
+    def getLogLevel(self):
+
+        try:
+            logLevel = int(utils.settings('logLevel'))
+        except ValueError:
+            logLevel = 0
+        
+        return logLevel
+
+    def getUserId(self):
+
+        username = self.getUsername()
+        w_userId = utils.window('emby_userId%s' % username)
+        s_userId = utils.settings('userId%s' % username)
+
+        # Verify the window property
+        if w_userId:
+            if not s_userId:
+                # Save access token if it's missing from settings
+                utils.settings('userId%s' % username, value=w_userId)
+            self.logMsg(
+                "Returning userId from WINDOW for username: %s UserId: %s"
+                % (username, w_userId), 2)
+            return w_userId
+        # Verify the settings
+        elif s_userId:
+            self.logMsg(
+                "Returning userId from SETTINGS for username: %s userId: %s"
+                % (username, s_userId), 2)
+            return s_userId
+        # No userId found
+        else:
+            self.logMsg("No userId saved for username: %s." % username, 1)
+
+    def getServer(self, prefix=True):
+
+        alternate = utils.settings('altip') == "true"
+        if alternate:
+            # Alternate host
+            HTTPS = utils.settings('secondhttps') == "true"
+            host = utils.settings('secondipaddress')
+            port = utils.settings('secondport')
+        else:
+            # Original host
+            HTTPS = utils.settings('https') == "true"
+            host = utils.settings('ipaddress')
+            port = utils.settings('port')
+
+        server = host + ":" + port
+        
+        if not host:
+            self.logMsg("No server information saved.", 2)
+            return False
+
+        # If https is true
+        if prefix and HTTPS:
+            server = "https://%s" % server
+            return server
+        # If https is false
+        elif prefix and not HTTPS:
+            server = "http://%s" % server
+            return server
+        # If only the host:port is required
+        elif not prefix:
+            return server
+
+    def getToken(self):
+
+        username = self.getUsername()
+        w_token = utils.window('emby_accessToken%s' % username)
+        s_token = utils.settings('accessToken')
+        
+        # Verify the window property
+        if w_token:
+            if not s_token:
+                # Save access token if it's missing from settings
+                utils.settings('accessToken', value=w_token)
+            self.logMsg(
+                "Returning accessToken from WINDOW for username: %s accessToken: %s"
+                % (username, w_token), 2)
+            return w_token
+        # Verify the settings
+        elif s_token:
+            self.logMsg(
+                "Returning accessToken from SETTINGS for username: %s accessToken: %s"
+                % (username, s_token), 2)
+            utils.window('emby_accessToken%s' % username, value=s_token)
+            return s_token
+        else:
+            self.logMsg("No token found.", 1)
+            return ""
+
+    def getSSLverify(self):
+        # Verify host certificate
+        s_sslverify = utils.settings('sslverify')
+        if utils.settings('altip') == "true":
+            s_sslverify = utils.settings('secondsslverify')
+
+        if s_sslverify == "true":
+            return True
+        else:
+            return False
+
+    def getSSL(self):
+        # Client side certificate
+        s_cert = utils.settings('sslcert')
+        if utils.settings('altip') == "true":
+            s_cert = utils.settings('secondsslcert')
+
+        if s_cert == "None":
+            return None
+        else:
+            return s_cert
+
+    def setUserPref(self):
+
+        doUtils = self.doUtils
+
+        url = "{server}/emby/Users/{UserId}?format=json"
+        result = doUtils.downloadUrl(url)
+        self.userSettings = result
+        # Set user image for skin display
+        if result.get('PrimaryImageTag'):
+            utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result, 'Primary'))
+
+        # Set resume point max
+        url = "{server}/emby/System/Configuration?format=json"
+        result = doUtils.downloadUrl(url)
+
+        utils.settings('markPlayed', value=str(result['MaxResumePct']))
+
+    def getPublicUsers(self):
+
+        server = self.getServer()
+
+        # Get public Users
+        url = "%s/emby/Users/Public?format=json" % server
+        result = self.doUtils.downloadUrl(url, authenticate=False)
+        
+        if result != "":
+            return result
+        else:
+            # Server connection failed
+            return False
+
+    def hasAccess(self):
+        # hasAccess is verified in service.py
+        url = "{server}/emby/Users?format=json"
+        result = self.doUtils.downloadUrl(url)
+        
+        if result == False:
+            # Access is restricted, set in downloadutils.py via exception
+            self.logMsg("Access is restricted.", 1)
+            self.HasAccess = False
+        
+        elif utils.window('emby_online') != "true":
+            # Server connection failed
+            pass
+
+        elif utils.window('emby_serverStatus') == "restricted":
+            self.logMsg("Access is granted.", 1)
+            self.HasAccess = True
+            utils.window('emby_serverStatus', clear=True)
+            xbmcgui.Dialog().notification("Emby server", "Access is enabled.")
+
+    def loadCurrUser(self, authenticated=False):
+
+        doUtils = self.doUtils
+        username = self.getUsername()
+        userId = self.getUserId()
+        
+        # Only to be used if token exists
+        self.currUserId = userId
+        self.currServer = self.getServer()
+        self.currToken = self.getToken()
+        self.ssl = self.getSSLverify()
+        self.sslcert = self.getSSL()
+
+        # Test the validity of current token
+        if authenticated == False:
+            url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
+            utils.window('emby_currUser', value=userId)
+            utils.window('emby_accessToken%s' % userId, value=self.currToken)
+            result = doUtils.downloadUrl(url)
+
+            if result == 401:
+                # Token is no longer valid
+                self.resetClient()
+                return False
+
+        # Set to windows property
+        utils.window('emby_currUser', value=userId)
+        utils.window('emby_accessToken%s' % userId, value=self.currToken)
+        utils.window('emby_server%s' % userId, value=self.currServer)
+        utils.window('emby_server_%s' % userId, value=self.getServer(prefix=False))
+
+        # Set DownloadUtils values
+        doUtils.setUsername(username)
+        doUtils.setUserId(self.currUserId)
+        doUtils.setServer(self.currServer)
+        doUtils.setToken(self.currToken)
+        doUtils.setSSL(self.ssl, self.sslcert)
+        # parental control - let's verify if access is restricted
+        self.hasAccess()
+        # Start DownloadUtils session
+        doUtils.startSession()
+        self.getAdditionalUsers()
+        # Set user preferences in settings
+        self.currUser = username
+        self.setUserPref()
+        
+
+    def authenticate(self):
+        # Get /profile/addon_data
+        addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
+        hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
+
+        username = self.getUsername()
+        server = self.getServer()
+
+        # If there's no settings.xml
+        if not hasSettings:
+            self.logMsg("No settings.xml found.", 1)
+            self.auth = False
+            return
+        # If no user information
+        elif not server or not username:
+            self.logMsg("Missing server information.", 1)
+            self.auth = False
+            return
+        # If there's a token, load the user
+        elif self.getToken():
+            result = self.loadCurrUser()
+
+            if result == False:
+                pass
+            else:
+                self.logMsg("Current user: %s" % self.currUser, 1)
+                self.logMsg("Current userId: %s" % self.currUserId, 1)
+                self.logMsg("Current accessToken: %s" % self.currToken, 2)
+                return
+        
+        ##### AUTHENTICATE USER #####
+
+        users = self.getPublicUsers()
+        password = ""
+        
+        # Find user in list
+        for user in users:
+            name = user['Name']
+
+            if username.decode('utf-8') in name:
+                # If user has password
+                if user['HasPassword'] == True:
+                    password = xbmcgui.Dialog().input(
+                        heading="Enter password for user: %s" % username,
+                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
+                    # If password dialog is cancelled
+                    if not password:
+                        self.logMsg("No password entered.", 0)
+                        utils.window('emby_serverStatus', value="Stop")
+                        self.auth = False
+                        return
+                break
+        else:
+            # Manual login, user is hidden
+            password = xbmcgui.Dialog().input(
+                                    heading="Enter password for user: %s" % username,
+                                    option=xbmcgui.ALPHANUM_HIDE_INPUT)
+        sha1 = hashlib.sha1(password)
+        sha1 = sha1.hexdigest()    
+
+        # Authenticate username and password
+        url = "%s/emby/Users/AuthenticateByName?format=json" % server
+        data = {'username': username, 'password': sha1}
+        self.logMsg(data, 2)
+
+        result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
+
+        try:
+            self.logMsg("Auth response: %s" % result, 1)
+            accessToken = result['AccessToken']
+        
+        except (KeyError, TypeError):
+            self.logMsg("Failed to retrieve the api key.", 1)
+            accessToken = None
+
+        if accessToken is not None:
+            self.currUser = username
+            xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser)
+            userId = result['User']['Id']
+            utils.settings('accessToken', value=accessToken)
+            utils.settings('userId%s' % username, value=userId)
+            self.logMsg("User Authenticated: %s" % accessToken, 1)
+            self.loadCurrUser(authenticated=True)
+            utils.window('emby_serverStatus', clear=True)
+            self.retry = 0
+        else:
+            self.logMsg("User authentication failed.", 1)
+            utils.settings('accessToken', value="")
+            utils.settings('userId%s' % username, value="")
+            xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.")
+            
+            # Give two attempts at entering password
+            if self.retry == 2:
+                self.logMsg(
+                    """Too many retries. You can retry by resetting 
+                    attempts in the addon settings.""", 1)
+                utils.window('emby_serverStatus', value="Stop")
+                xbmcgui.Dialog().ok(
+                    heading="Error connecting",
+                    line1="Failed to authenticate too many times.",
+                    line2="You can retry by resetting attempts in the addon settings.")
+
+            self.retry += 1
+            self.auth = False
+
+    def resetClient(self):
+
+        self.logMsg("Reset UserClient authentication.", 1)
+        username = self.getUsername()
+        
+        if self.currToken is not None:
+            # In case of 401, removed saved token
+            utils.settings('accessToken', value="")
+            utils.window('emby_accessToken%s' % username, clear=True)
+            self.currToken = None
+            self.logMsg("User token has been removed.", 1)
+        
+        self.auth = True
+        self.currUser = None
+        
+    def run(self):
+
+        monitor = xbmc.Monitor()
+        self.logMsg("----===## Starting UserClient ##===----", 0)
+
+        while not monitor.abortRequested():
+
+            # Verify the log level
+            currLogLevel = self.getLogLevel()
+            if self.logLevel != currLogLevel:
+                # Set new log level
+                self.logLevel = currLogLevel
+                utils.window('emby_logLevel', value=str(currLogLevel))
+                self.logMsg("New Log Level: %s" % currLogLevel, 0)
+
+
+            status = utils.window('emby_serverStatus')
+            if status:
+                # Verify the connection status to server
+                if status == "restricted":
+                    # Parental control is restricting access
+                    self.HasAccess = False
+
+                elif status == "401":
+                    # Unauthorized access, revoke token
+                    utils.window('emby_serverStatus', value="Auth")
+                    self.resetClient()
+
+            if self.auth and (self.currUser is None):
+                # Try to authenticate user
+                status = utils.window('emby_serverStatus')
+                if not status or status == "Auth":
+                    # Set auth flag because we no longer need
+                    # to authenticate the user
+                    self.auth = False
+                    self.authenticate()
+                
+
+            if not self.auth and (self.currUser is None):
+                # If authenticate failed.
+                server = self.getServer()
+                username = self.getUsername()
+                status = utils.window('emby_serverStatus')
+                
+                # The status Stop is for when user cancelled password dialog.
+                if server and username and status != "Stop":
+                    # Only if there's information found to login
+                    self.logMsg("Server found: %s" % server, 2)
+                    self.logMsg("Username found: %s" % username, 2)
+                    self.auth = True
+
+
+            if self.stopClient == True:
+                # If stopping the client didn't work
+                break
+                
+            if monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                break
+        
+        self.doUtils.stopSession()    
+        self.logMsg("##===---- UserClient Stopped ----===##", 0)
+
+    def stopClient(self):
+        # When emby for kodi terminates
+        self.stopClient = True
\ No newline at end of file
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
new file mode 100644
index 00000000..83e73e1d
--- /dev/null
+++ b/resources/lib/utils.py
@@ -0,0 +1,487 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import cProfile
+import inspect
+import pstats
+import sqlite3
+import time
+import unicodedata
+import xml.etree.ElementTree as etree
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+
+#################################################################################################
+
+
+def logMsg(title, msg, level=1):
+    
+    # Get the logLevel set in UserClient
+    try:
+        logLevel = int(window('emby_logLevel'))
+    except ValueError:
+        logLevel = 0
+    
+    if logLevel >= level:
+        
+        if logLevel == 2: # inspect.stack() is expensive
+            try:
+                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
+            except UnicodeEncodeError:
+                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
+        else:
+            try:
+                xbmc.log("%s -> %s" % (title, msg))
+            except UnicodeEncodeError:
+                xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
+
+def window(property, value=None, clear=False, windowid=10000):
+    # Get or set window property
+    WINDOW = xbmcgui.Window(windowid)
+    
+    if clear:
+        WINDOW.clearProperty(property)
+    elif value is not None:
+        WINDOW.setProperty(property, value)
+    else:
+        return WINDOW.getProperty(property)
+
+def settings(setting, value=None):
+    # Get or add addon setting
+    addon = xbmcaddon.Addon(id='plugin.video.emby')
+    
+    if value is not None:
+        addon.setSetting(setting, value)
+    else:
+        return addon.getSetting(setting)
+
+def language(stringid):
+    # Central string retrieval
+    addon = xbmcaddon.Addon(id='plugin.video.emby')
+    string = addon.getLocalizedString(stringid)
+
+    return string
+
+def kodiSQL(type="video"):
+    
+    if type == "emby":
+        dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
+    elif type == "music":
+        dbPath = getKodiMusicDBPath()
+    elif type == "texture":
+        dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
+    else:
+        dbPath = getKodiVideoDBPath()
+    
+    connection = sqlite3.connect(dbPath)
+    return connection
+
+def getKodiVideoDBPath():
+
+    kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
+    dbVersion = {
+
+        "13": 78,   # Gotham
+        "14": 90,   # Helix
+        "15": 93,   # Isengard
+        "16": 99    # Jarvis
+    }
+
+    dbPath = xbmc.translatePath(
+                    "special://database/MyVideos%s.db"
+                    % dbVersion.get(kodibuild, "")).decode('utf-8')
+    return dbPath
+
+def getKodiMusicDBPath():
+
+    kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
+    dbVersion = {
+
+        "13": 46,   # Gotham
+        "14": 48,   # Helix
+        "15": 52,   # Isengard
+        "16": 56    # Jarvis
+    }
+
+    dbPath = xbmc.translatePath(
+                    "special://database/MyMusic%s.db"
+                    % dbVersion.get(kodibuild, "")).decode('utf-8')
+    return dbPath
+
+def reset():
+
+    dialog = xbmcgui.Dialog()
+
+    resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
+    if resp == 0:
+        return
+
+    # first stop any db sync
+    window('emby_shouldStop', value="true")
+    count = 10
+    while window('emby_dbScan') == "true":
+        logMsg("EMBY", "Sync is running, will retry: %s..." % count)
+        count -= 1
+        if count == 0:
+            dialog.ok("Warning", "Could not stop the database from running. Try again.")
+            return
+        xbmc.sleep(1000)
+
+    # Clean up the playlists
+    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+    dirs, files = xbmcvfs.listdir(path)
+    for file in files:
+        if file.startswith('Emby'):
+            xbmcvfs.delete("%s%s" % (path, file))
+
+    # Clean up the video nodes
+    import shutil
+    path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+    dirs, files = xbmcvfs.listdir(path)
+    for dir in dirs:
+        if dir.startswith('Emby'):
+            shutil.rmtree("%s%s" % (path, dir))
+    for file in files:
+        if file.startswith('emby'):
+            xbmcvfs.delete("%s%s" % (path, file))
+
+    # Wipe the kodi databases
+    logMsg("EMBY", "Resetting the Kodi video database.")
+    connection = kodiSQL('video')
+    cursor = connection.cursor()
+    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+    rows = cursor.fetchall()
+    for row in rows:
+        tablename = row[0]
+        if tablename != "version":
+            cursor.execute("DELETE FROM " + tablename)
+    connection.commit()
+    cursor.close()
+
+    if settings('disableMusic') != "true":
+        logMsg("EMBY", "Resetting the Kodi music database.")
+        connection = kodiSQL('music')
+        cursor = connection.cursor()
+        cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+        rows = cursor.fetchall()
+        for row in rows:
+            tablename = row[0]
+            if tablename != "version":
+                cursor.execute("DELETE FROM " + tablename)
+        connection.commit()
+        cursor.close()
+
+    # Wipe the emby database
+    logMsg("EMBY", "Resetting the Emby database.")
+    connection = kodiSQL('emby')
+    cursor = connection.cursor()
+    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+    rows = cursor.fetchall()
+    for row in rows:
+        tablename = row[0]
+        if tablename != "version":
+            cursor.execute("DELETE FROM " + tablename)
+    connection.commit()
+    cursor.close()
+    
+    # reset the install run flag  
+    settings('SyncInstallRunDone', value="false")
+
+    # Remove emby info
+    resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
+    if resp == 1:
+        # Delete the settings
+        addon = xbmcaddon.Addon()
+        addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
+        dataPath = "%ssettings.xml" % addondir
+        xbmcvfs.delete(dataPath)
+        logMsg("EMBY", "Deleting: settings.xml", 1)
+
+    dialog.ok(
+        heading="Emby for Kodi",
+        line1="Database reset has completed, Kodi will now restart to apply the changes.")
+    xbmc.executebuiltin('RestartApp')
+
+def startProfiling():
+    
+    pr = cProfile.Profile()
+    pr.enable()
+    
+    return pr
+
+def stopProfiling(pr, profileName):
+    
+    pr.disable()
+    ps = pstats.Stats(pr)
+    
+    profiles = xbmc.translatePath("%sprofiles/"
+                % xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
+
+    if not xbmcvfs.exists(profiles):
+        # Create the profiles folder
+        xbmcvfs.mkdir(profiles)
+
+    timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
+    profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
+    
+    f = open(profile, 'wb')
+    f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
+    for (key, value) in ps.stats.items():
+        (filename, count, func_name) = key
+        (ccalls, ncalls, total_time, cumulative_time, callers) = value
+        try:
+            f.write(
+                "%s\t%s\t%s\t%s\t%s\r\n"
+                % (ncalls, "{:10.4f}".format(total_time),
+                    "{:10.4f}".format(cumulative_time), func_name, filename))
+        except ValueError:
+            f.write(
+                "%s\t%s\t%s\t%s\t%s\r\n"
+                % (ncalls, "{0}".format(total_time),
+                    "{0}".format(cumulative_time), func_name, filename))
+    f.close()
+
+def normalize_nodes(text):
+    # For video nodes
+    text = text.replace(":", "")
+    text = text.replace("/", "-")
+    text = text.replace("\\", "-")
+    text = text.replace("<", "")
+    text = text.replace(">", "")
+    text = text.replace("*", "")
+    text = text.replace("?", "")
+    text = text.replace('|', "")
+    text = text.replace('(', "")
+    text = text.replace(')', "")
+    text = text.strip()
+    # Remove dots from the last character as windows can not have directories
+    # with dots at the end
+    text = text.rstrip('.')
+    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
+    
+    return text
+
+def normalize_string(text):
+    # For theme media, do not modify unless
+    # modified in TV Tunes
+    text = text.replace(":", "")
+    text = text.replace("/", "-")
+    text = text.replace("\\", "-")
+    text = text.replace("<", "")
+    text = text.replace(">", "")
+    text = text.replace("*", "")
+    text = text.replace("?", "")
+    text = text.replace('|', "")
+    text = text.strip()
+    # Remove dots from the last character as windows can not have directories
+    # with dots at the end
+    text = text.rstrip('.')
+    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
+
+    return text
+
+def indent(elem, level=0):
+    # Prettify xml trees
+    i = "\n" + level*"  "
+    if len(elem):
+        if not elem.text or not elem.text.strip():
+          elem.text = i + "  "
+        if not elem.tail or not elem.tail.strip():
+          elem.tail = i
+        for elem in elem:
+          indent(elem, level+1)
+        if not elem.tail or not elem.tail.strip():
+          elem.tail = i
+    else:
+        if level and (not elem.tail or not elem.tail.strip()):
+          elem.tail = i
+
+def sourcesXML():
+    # To make Master lock compatible
+    path = xbmc.translatePath("special://profile/").decode('utf-8')
+    xmlpath = "%ssources.xml" % path
+
+    try:
+        xmlparse = etree.parse(xmlpath)
+    except: # Document is blank or missing
+        root = etree.Element('sources')
+    else:
+        root = xmlparse.getroot()
+        
+
+    video = root.find('video')
+    if video is None:
+        video = etree.SubElement(root, 'video')
+        etree.SubElement(video, 'default', attrib={'pathversion': "1"})
+
+    # Add elements
+    for i in range(1, 3):
+
+            for source in root.findall('.//path'):
+                if source.text == "smb://embydummy/dummypath%s/" % i:
+                    # Already there, skip
+                    break
+            else:
+                source = etree.SubElement(video, 'source')
+                etree.SubElement(source, 'name').text = "Emby"
+                etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = (
+
+                    "smb://embydummy/dummypath%s/" % i
+                )
+                etree.SubElement(source, 'allowsharing').text = "true"
+    # Prettify and write to file
+    try:
+        indent(root)
+    except: pass
+    etree.ElementTree(root).write(xmlpath)
+
+def passwordsXML():
+
+    # To add network credentials
+    path = xbmc.translatePath("special://userdata/").decode('utf-8')
+    xmlpath = "%spasswords.xml" % path
+
+    try:
+        xmlparse = etree.parse(xmlpath)
+    except: # Document is blank or missing
+        root = etree.Element('passwords')
+    else:
+        root = xmlparse.getroot()
+
+    dialog = xbmcgui.Dialog()
+    credentials = settings('networkCreds')
+    if credentials:
+        # Present user with options
+        option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
+
+        if option < 0:
+            # User cancelled dialog
+            return
+
+        elif option == 1:
+            # User selected remove
+            iterator = root.getiterator('passwords')
+
+            for paths in iterator:
+                for path in paths:
+                    if path.find('.//from').text == "smb://%s/" % credentials:
+                        paths.remove(path)
+                        logMsg("EMBY", "Successfully removed credentials for: %s"
+                                % credentials, 1)
+                        etree.ElementTree(root).write(xmlpath)
+                        break
+            else:
+                logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
+            
+            settings('networkCreds', value="")
+            xbmcgui.Dialog().notification(
+                                heading="Emby for Kodi",
+                                message="%s removed from passwords.xml!" % credentials,
+                                icon="special://home/addons/plugin.video.emby/icon.png",
+                                time=1000,
+                                sound=False)
+            return
+
+        elif option == 0:
+            # User selected to modify
+            server = dialog.input("Modify the computer name or ip address", credentials)
+            if not server:
+                return
+    else:
+        # No credentials added
+        dialog.ok(
+            heading="Network credentials",
+            line1= (
+                "Input the server name or IP address as indicated in your emby library paths. "
+                'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
+        server = dialog.input("Enter the server name or IP address", settings('ipaddress'))
+        if not server:
+            return
+
+    # Network username
+    user = dialog.input("Enter the network username")
+    if not user:
+        return
+    # Network password
+    password = dialog.input(
+                        heading="Enter the network password",
+                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
+    if not password:
+        return
+
+    # Add elements
+    for path in root.findall('.//path'):
+        if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
+            # Found the server, rewrite credentials
+            path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server)
+            break
+    else:
+        # Server not found, add it.
+        path = etree.SubElement(root, 'path')
+        etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
+        topath = "smb://%s:%s@%s/" % (user, password, server)
+        etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
+        # Force Kodi to see the credentials without restarting
+        xbmcvfs.exists(topath)
+
+    # Add credentials    
+    settings('networkCreds', value="%s" % server)
+    logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
+    # Prettify and write to file
+    try:
+        indent(root)
+    except: pass
+    etree.ElementTree(root).write(xmlpath)
+    
+    dialog.notification(
+            heading="Emby for Kodi",
+            message="%s added to passwords.xml!" % server,
+            icon="special://home/addons/plugin.video.emby/icon.png",
+            time=1000,
+            sound=False)
+
+def playlistXSP(mediatype, tagname, viewtype="", delete=False):
+    # Tagname is in unicode - actions: add or delete
+    tagname = tagname.encode('utf-8')
+    cleantagname = normalize_nodes(tagname)
+    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+    if viewtype == "mixed":
+        plname = "%s - %s" % (tagname, mediatype)
+        xsppath = "%sEmby %s - %s.xsp" % (path, cleantagname, mediatype)
+    else:
+        plname = tagname
+        xsppath = "%sEmby %s.xsp" % (path, cleantagname)
+
+    # Create the playlist directory
+    if not xbmcvfs.exists(path):
+        xbmcvfs.mkdirs(path)
+
+    # Only add the playlist if it doesn't already exists
+    if xbmcvfs.exists(xsppath):
+
+        if delete:
+            xbmcvfs.delete(xsppath)
+            logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
+        
+        return
+
+    # Using write process since there's no guarantee the xml declaration works with etree
+    itemtypes = {
+        'homevideos': "movies"
+    }
+    f = open(xsppath, 'w')
+    f.write(
+        '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
+        '<smartplaylist type="%s">\n\t'
+            '<name>Emby %s</name>\n\t'
+            '<match>all</match>\n\t'
+            '<rule field="tag" operator="is">\n\t\t'
+                '<value>%s</value>\n\t'
+            '</rule>'
+        % (itemtypes.get(mediatype, mediatype), plname, tagname))
+    f.close()
+    logMsg("EMBY", "Successfully added playlist: %s" % tagname)
\ No newline at end of file
diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py
new file mode 100644
index 00000000..1dc9d5a6
--- /dev/null
+++ b/resources/lib/videonodes.py
@@ -0,0 +1,344 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import shutil
+import xml.etree.ElementTree as etree
+
+import xbmc
+import xbmcaddon
+import xbmcvfs
+
+import clientinfo
+import utils
+
+#################################################################################################
+
+
+class VideoNodes(object):
+
+
+    def __init__(self):
+
+        clientInfo = clientinfo.ClientInfo()
+        self.addonName = clientInfo.getAddonName()
+
+        self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def commonRoot(self, order, label, tagname, roottype=1):
+
+        if roottype == 0:
+            # Index
+            root = etree.Element('node', attrib={'order': "%s" % order})
+        elif roottype == 1:
+            # Filter
+            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
+            etree.SubElement(root, 'match').text = "all"
+            # Add tag rule
+            rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
+            etree.SubElement(rule, 'value').text = tagname
+        else:
+            # Folder
+            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
+
+        etree.SubElement(root, 'label').text = label
+        etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
+
+        return root
+
+    def viewNode(self, indexnumber, tagname, mediatype, viewtype, delete=False):
+
+        kodiversion = self.kodiversion
+
+        if mediatype == "homevideos":
+            # Treat homevideos as movies
+            mediatype = "movies"
+
+        tagname = tagname.encode('utf-8')
+        cleantagname = utils.normalize_nodes(tagname)
+        if viewtype == "mixed":
+            dirname = "%s - %s" % (cleantagname, mediatype)
+        else:
+            dirname = cleantagname
+        
+        path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+        nodepath = xbmc.translatePath(
+                    "special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
+
+        # Verify the video directory
+        if not xbmcvfs.exists(path):
+            shutil.copytree(
+                src=xbmc.translatePath("special://xbmc/system/library/video/").decode('utf-8'),
+                dst=xbmc.translatePath("special://profile/library/video/").decode('utf-8'))
+            xbmcvfs.exists(path)
+
+        # Create the node directory
+        if not xbmcvfs.exists(nodepath):
+            # We need to copy over the default items
+            xbmcvfs.mkdirs(nodepath)
+        else:
+            if delete:
+                dirs, files = xbmcvfs.listdir(nodepath)
+                for file in files:
+                    xbmcvfs.delete(nodepath + file)
+
+                self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
+                return
+
+        # Create index entry
+        nodeXML = "%sindex.xml" % nodepath
+        # Set windows property
+        path = "library://video/Emby - %s/" % dirname
+        for i in range(1, indexnumber):
+            # Verify to make sure we don't create duplicates
+            if utils.window('Emby.nodes.%s.index' % i) == path:
+                return
+
+        utils.window('Emby.nodes.%s.index' % indexnumber, value=path)
+        # Root
+        root = self.commonRoot(order=0, label=dirname, tagname=tagname, roottype=0)
+        try:
+            utils.indent(root)
+        except: pass
+        etree.ElementTree(root).write(nodeXML)
+
+
+        nodetypes = {
+
+            '1': "all",
+            '2': "recent",
+            '3': "recentepisodes",
+            '4': "inprogress",
+            '5': "inprogressepisodes",
+            '6': "unwatched",
+            '7': "nextupepisodes",
+            '8': "sets",
+            '9': "genres",
+            '10': "random",
+            '11': "recommended"
+        }
+        mediatypes = {
+            # label according to nodetype per mediatype
+            'movies': {
+                '1': tagname,
+                '2': 30174,
+                '4': 30177,
+                '6': 30189,
+                '8': 20434,
+                '9': 135,
+                '10': 30229,
+                '11': 30230},
+
+            'tvshows': {
+                '1': tagname,
+                '2': 30170,
+                '3': 30175,
+                '4': 30171,
+                '5': 30178,
+                '7': 30179,
+                '9': 135,
+                '10': 30229,
+                '11': 30230},
+        }
+
+        nodes = mediatypes[mediatype]
+        for node in nodes:
+
+            nodetype = nodetypes[node]
+            nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
+            # Get label
+            stringid = nodes[node]
+            if node != '1':
+                label = utils.language(stringid)
+                if not label:
+                    label = xbmc.getLocalizedString(stringid)
+            else:
+                label = stringid
+
+            # Set window properties
+            if nodetype == "nextupepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
+            elif kodiversion == 14 and nodetype == "recentepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
+            elif kodiversion == 14 and nodetype == "inprogressepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
+            else:
+                path = "library://video/Emby - %s/%s_%s.xml" % (dirname, cleantagname, nodetype)
+            windowpath = "ActivateWindow(Video, %s, return)" % path
+            
+            if nodetype == "all":
+
+                if viewtype == "mixed":
+                    templabel = dirname
+                else:
+                    templabel = label
+
+                embynode = "Emby.nodes.%s" % indexnumber
+                utils.window('%s.title' % embynode, value=templabel)
+                utils.window('%s.path' % embynode, value=windowpath)
+                utils.window('%s.content' % embynode, value=path)
+                utils.window('%s.type' % embynode, value=mediatype)
+            else:
+                embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
+                utils.window('%s.title' % embynode, value=label)
+                utils.window('%s.path' % embynode, value=windowpath)
+                utils.window('%s.content' % embynode, value=path)
+
+            if xbmcvfs.exists(nodeXML):
+                # Don't recreate xml if already exists
+                continue
+
+
+            # Create the root
+            if nodetype == "nextupepisodes" or (kodiversion == 14 and
+                                        nodetype in ('recentepisodes', 'inprogressepisodes')):
+                # Folder type with plugin path
+                root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
+                etree.SubElement(root, 'path').text = path
+                etree.SubElement(root, 'content').text = "episodes"
+            else:
+                root = self.commonRoot(order=node, label=label, tagname=tagname)
+                if nodetype in ('recentepisodes', 'inprogressepisodes'):
+                    etree.SubElement(root, 'content').text = "episodes"
+                else:
+                    etree.SubElement(root, 'content').text = mediatype
+
+                limit = "25"
+                # Elements per nodetype
+                if nodetype == "all":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                
+                elif nodetype == "recent":
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+                
+                elif nodetype == "inprogress":
+                    etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
+                    etree.SubElement(root, 'limit').text = limit
+
+                elif nodetype == "genres":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    etree.SubElement(root, 'group').text = "genres"
+                
+                elif nodetype == "unwatched":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+
+                elif nodetype == "sets":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    etree.SubElement(root, 'group').text = "sets"
+
+                elif nodetype == "random":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
+                    etree.SubElement(root, 'limit').text = limit
+
+                elif nodetype == "recommended":
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+                    rule2 = etree.SubElement(root, 'rule',
+                        attrib={'field': "rating", 'operator': "greaterthan"})
+                    etree.SubElement(rule2, 'value').text = "7"
+
+                elif nodetype == "recentepisodes":
+                    # Kodi Isengard, Jarvis
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+
+                elif nodetype == "inprogressepisodes":
+                    # Kodi Isengard, Jarvis
+                    etree.SubElement(root, 'limit').text = "25"
+                    rule = etree.SubElement(root, 'rule',
+                        attrib={'field': "inprogress", 'operator':"true"})
+
+            try:
+                utils.indent(root)
+            except: pass
+            etree.ElementTree(root).write(nodeXML)
+
+    def singleNode(self, indexnumber, tagname, mediatype, itemtype):
+
+        tagname = tagname.encode('utf-8')
+        cleantagname = utils.normalize_nodes(tagname)
+        nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+        nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
+        path = "library://video/emby_%s.xml" % (cleantagname)
+        windowpath = "ActivateWindow(Video, %s, return)" % path
+        
+        # Create the video node directory
+        if not xbmcvfs.exists(nodepath):
+            # We need to copy over the default items
+            shutil.copytree(
+                src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
+                dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
+            xbmcvfs.exists(path)
+
+        labels = {
+
+            'Favorite movies': 30180,
+            'Favorite tvshows': 30181,
+            'channels': 30173
+        }
+        label = utils.language(labels[tagname])
+        embynode = "Emby.nodes.%s" % indexnumber
+        utils.window('%s.title' % embynode, value=label)
+        utils.window('%s.path' % embynode, value=windowpath)
+        utils.window('%s.content' % embynode, value=path)
+        utils.window('%s.type' % embynode, value=itemtype)
+
+        if xbmcvfs.exists(nodeXML):
+            # Don't recreate xml if already exists
+            return
+
+        if itemtype == "channels":
+            root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
+            etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
+        else:
+            root = self.commonRoot(order=1, label=label, tagname=tagname)
+            etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+
+        etree.SubElement(root, 'content').text = mediatype
+
+        try:
+            utils.indent(root)
+        except: pass
+        etree.ElementTree(root).write(nodeXML)
+
+    def clearProperties(self):
+
+        self.logMsg("Clearing nodes properties.", 1)
+        embyprops = utils.window('Emby.nodes.total')
+        propnames = [
+        
+            "index","path","title","content",
+            "inprogress.content","inprogress.title",
+            "inprogress.content","inprogress.path",
+            "nextepisodes.title","nextepisodes.content",
+            "nextepisodes.path","unwatched.title",
+            "unwatched.content","unwatched.path",
+            "recent.title","recent.content","recent.path",
+            "recentepisodes.title","recentepisodes.content",
+            "recentepisodes.path","inprogressepisodes.title",
+            "inprogressepisodes.content","inprogressepisodes.path"
+        ]
+
+        if embyprops:
+            totalnodes = int(embyprops)
+            for i in range(totalnodes):
+                for prop in propnames:
+                    utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
\ No newline at end of file