diff --git a/default.py b/default.py
index 9d9c098b..e11c867c 100644
--- a/default.py
+++ b/default.py
@@ -72,7 +72,8 @@ class Main():
             'recentepisodes': entrypoint.getRecentEpisodes,
             'refreshplaylist': entrypoint.refreshPlaylist,
             'deviceid': entrypoint.resetDeviceId,
-            'delete': entrypoint.deleteItem
+            'delete': entrypoint.deleteItem,
+            'connect': entrypoint.emby_connect
         }
         
         if "/extrafanart" in sys.argv[0]:
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
index 1f626dd9..affdc2da 100644
--- a/resources/language/English/strings.xml
+++ b/resources/language/English/strings.xml
@@ -3,13 +3,13 @@
 
     <!-- Add-on settings -->
     <string id="29999">Emby for Kodi</string>
-    <string id="30000">Primary Server Address</string><!-- Verified -->
-    <string id="30002">Play from HTTP instead of SMB</string><!-- Verified -->
-    <string id="30004">Log level</string><!-- Verified -->
-    <string id="30016">Device Name</string><!-- Verified -->
+    <string id="30000">Primary Server Address</string>
+    <string id="30002">Play from HTTP instead of SMB</string>
+    <string id="30004">Log level</string>
+    <string id="30016">Device Name</string>
     <string id="30022">Advanced</string>
-    <string id="30024">Username</string><!-- Verified -->
-    <string id="30030">Port Number</string><!-- Verified -->
+    <string id="30024">Username</string>
+    <string id="30030">Port Number</string>
 
     <string id="30035">Number of recent Music Albums to show:</string>
     <string id="30036">Number of recent Movies to show:</string>
@@ -232,12 +232,23 @@
 
     <!-- dialogs -->
     <string id="30600">Sign in with Emby Connect</string>
-    <string id="30601">Username or email:</string>
-    <string id="30602">Password:</string>
+    <string id="30602">Password</string>
     <string id="30603">Please see our terms of use. The use of any Emby software constitutes acceptance of these terms.</string>
     <string id="30604">Scan me</string>
     <string id="30605">Sign in</string>
-    <string id="30606">Remind me later</string>
+    <string id="30606">Cancel</string>
+    <string id="30607">Select main server</string>
+    <string id="30608">Username or password cannot be empty</string>
+    <string id="30609">Unable to connect to the selected server</string>
+    <string id="30610">Connect to</string><!-- Connect to {server} -->
+    <string id="30611">Manually add server</string>
+    <string id="30612">Please sign in</string>
+    <string id="30613">Username cannot be empty</string>
+    <string id="30614">Connect to server</string>
+    <string id="30615">Host</string>
+    <string id="30616">Connect</string>
+    <string id="30617">Server or port cannot be empty</string>
+    <string id="30618">Change Emby Connect user</string>
 
     <!-- service add-on -->
     <string id="33000">Welcome</string>
diff --git a/resources/lib/connect.py b/resources/lib/connect.py
deleted file mode 100644
index aa3082a7..00000000
--- a/resources/lib/connect.py
+++ /dev/null
@@ -1,257 +0,0 @@
-# -*- coding: utf-8 -*-
-
-##################################################################################################
-
-import json
-import requests
-import logging
-
-import clientinfo
-from utils import window
-
-##################################################################################################
-
-# Disable requests logging
-from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning
-requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
-requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
-
-log = logging.getLogger("EMBY."+__name__)
-
-##################################################################################################
-
-
-class ConnectUtils():
-
-    # Borg - multiple instances, shared state
-    _shared_state = {}
-    clientInfo = clientinfo.ClientInfo()
-
-    # Requests session
-    c = None
-    timeout = 30
-
-
-    def __init__(self):
-
-        self.__dict__ = self._shared_state
-
-
-    def setUserId(self, userId):
-        # Reserved for userclient only
-        self.userId = userId
-        log.debug("Set connect userId: %s" % userId)
-
-    def setServer(self, server):
-        # Reserved for userclient only
-        self.server = server
-        log.debug("Set connect server: %s" % server)
-
-    def setToken(self, token):
-        # Reserved for userclient only
-        self.token = token
-        log.debug("Set connect token: %s" % token)
-
-
-    def startSession(self):
-
-        self.deviceId = self.clientInfo.getDeviceId()
-
-        # User is identified from this point
-        # Attach authenticated header to the session
-        verify = False
-        header = self.getHeader()
-
-        # If user enabled host certificate verification
-        try:
-            verify = self.sslverify
-            if self.sslclient is not None:
-                verify = self.sslclient
-        except:
-            log.info("Could not load SSL settings.")
-
-        # Start session
-        self.c = requests.Session()
-        self.c.headers = header
-        self.c.verify = verify
-        # Retry connections to the server
-        self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
-        self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
-
-        log.info("Requests session started on: %s" % self.server)
-
-    def stopSession(self):
-        try:
-            self.c.close()
-        except Exception:
-            log.warn("Requests session could not be terminated")
-
-    def getHeader(self, authenticate=True):
-
-        version = self.clientInfo.getVersion()
-
-        if not authenticate:
-            # If user is not authenticated
-            header = {
-
-                'X-Application': "Kodi/%s" % version,
-                'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
-                'Accept': "application/json"
-            }
-            log.info("Header: %s" % header)
-
-        else:
-            token = self.token
-            # Attached to the requests session
-            header = {
-
-                'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
-                'Accept': "application/json",
-                'X-Application': "Kodi/%s" % version,
-                'X-Connect-UserToken': token
-            }
-            log.info("Header: %s" % header)
-
-        return header
-
-    def doUrl(self, url, data=None, postBody=None, rtype="GET",
-                parameters=None, authenticate=True, timeout=None):
-
-        log.debug("=== ENTER connectUrl ===")
-        
-        default_link = ""
-        
-        if timeout is None:
-            timeout = self.timeout
-
-        # Get requests session
-        try:
-            # If connect user is authenticated
-            if authenticate:
-                try:
-                    c = self.c
-                    # Replace for the real values
-                    url = url.replace("{server}", self.server)
-                    url = url.replace("{UserId}", self.userId)
-
-                    # Prepare request
-                    if rtype == "GET":
-                        r = c.get(url, json=postBody, params=parameters, timeout=timeout)
-                    elif rtype == "POST":
-                        r = c.post(url, data=data, timeout=timeout)
-                    elif rtype == "DELETE":
-                        r = c.delete(url, json=postBody, timeout=timeout)
-
-                except AttributeError:
-                    # request session does not exists
-                    self.server = "https://connect.emby.media/service"
-                    self.userId = window('embyco_currUser')
-                    self.token = window('embyco_accessToken%s' % self.userId)
-
-                    header = self.getHeader()
-                    verifyssl = False
-
-                    # If user enables ssl verification
-                    try:
-                        verifyssl = self.sslverify
-                        if self.sslclient is not None:
-                            verifyssl = self.sslclient
-                    except AttributeError:
-                        pass
-
-                    # Prepare request
-                    if rtype == "GET":
-                        r = requests.get(url,
-                                        json=postBody,
-                                        params=parameters,
-                                        headers=header,
-                                        timeout=timeout,
-                                        verify=verifyssl)
-
-                    elif rtype == "POST":
-                        r = requests.post(url,
-                                        data=data,
-                                        headers=header,
-                                        timeout=timeout,
-                                        verify=verifyssl)
-            # If user is not authenticated
-            else:
-                header = self.getHeader(authenticate=False)
-                verifyssl = False
-
-                # If user enables ssl verification
-                try:
-                    verifyssl = self.sslverify
-                    if self.sslclient is not None:
-                        verifyssl = self.sslclient
-                except AttributeError:
-                    pass
-
-                # Prepare request
-                if rtype == "GET":
-                    r = requests.get(url,
-                                    json=postBody,
-                                    params=parameters,
-                                    headers=header,
-                                    timeout=timeout,
-                                    verify=verifyssl)
-
-                elif rtype == "POST":
-                    r = requests.post(url,
-                                    data=data,
-                                    headers=header,
-                                    timeout=timeout,
-                                    verify=verifyssl)
-
-            ##### THE RESPONSE #####
-            log.info(r.url)
-            log.info(r)
-
-            if r.status_code == 204:
-                # No body in the response
-                log.info("====== 204 Success ======")
-
-            elif r.status_code == requests.codes.ok:
-
-                try:
-                    # UNICODE - JSON object
-                    r = r.json()
-                    log.info("====== 200 Success ======")
-                    log.info("Response: %s" % r)
-                    return r
-
-                except:
-                    if r.headers.get('content-type') != "text/html":
-                        log.info("Unable to convert the response for: %s" % url)
-            else:
-                r.raise_for_status()
-
-        ##### EXCEPTIONS #####
-
-        except requests.exceptions.ConnectionError as e:
-            # Make the addon aware of status
-            pass
-
-        except requests.exceptions.ConnectTimeout as e:
-            log.warn("Server timeout at: %s" % url)
-
-        except requests.exceptions.HTTPError as e:
-
-            if r.status_code == 401:
-                # Unauthorized
-                pass
-
-            elif r.status_code in (301, 302):
-                # Redirects
-                pass
-            elif r.status_code == 400:
-                # Bad requests
-                pass
-
-        except requests.exceptions.SSLError as e:
-            log.warn("Invalid SSL certificate for: %s" % url)
-
-        except requests.exceptions.RequestException as e:
-            log.warn("Unknown error connecting to: %s" % url)
-
-        return default_link
\ No newline at end of file
diff --git a/resources/lib/connect/__init__.py b/resources/lib/connect/__init__.py
new file mode 100644
index 00000000..b93054b3
--- /dev/null
+++ b/resources/lib/connect/__init__.py
@@ -0,0 +1 @@
+# Dummy file to make this directory a package.
diff --git a/resources/lib/connect/connectionmanager.py b/resources/lib/connect/connectionmanager.py
new file mode 100644
index 00000000..11dc2514
--- /dev/null
+++ b/resources/lib/connect/connectionmanager.py
@@ -0,0 +1,802 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import hashlib
+import json
+import logging
+import requests
+import socket
+import time
+from datetime import datetime
+
+import credentials as cred
+
+#################################################################################################
+
+# Disable requests logging
+from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning, SNIMissingWarning
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
+requests.packages.urllib3.disable_warnings(SNIMissingWarning)
+
+log = logging.getLogger("EMBY."+__name__.split('.')[-1])
+
+#################################################################################################
+
+ConnectionState = {
+    'Unavailable': 0,
+    'ServerSelection': 1,
+    'ServerSignIn': 2,
+    'SignedIn': 3,
+    'ConnectSignIn': 4,
+    'ServerUpdateNeeded': 5
+}
+
+ConnectionMode = {
+    'Local': 0,
+    'Remote': 1,
+    'Manual': 2
+}
+
+#################################################################################################
+
+def getServerAddress(server, mode):
+
+    modes = {
+        ConnectionMode['Local']: server.get('LocalAddress'),
+        ConnectionMode['Remote']: server.get('RemoteAddress'),
+        ConnectionMode['Manual']: server.get('ManualAddress')
+    }
+    return (modes.get(mode) or 
+            server.get('ManualAddress',server.get('LocalAddress',server.get('RemoteAddress'))))
+
+
+class ConnectionManager(object):
+
+    default_timeout = 20
+    apiClients = []
+    minServerVersion = "3.0.5930"
+    connectUser = None
+
+
+    def __init__(self, appName, appVersion, deviceName, deviceId, capabilities=None, devicePixelRatio=None):
+        
+        log.info("Begin ConnectionManager constructor")
+
+        self.credentialProvider = cred.Credentials()
+        self.appName = appName
+        self.appVersion = appVersion
+        self.deviceName = deviceName
+        self.deviceId = deviceId
+        self.capabilities = capabilities
+        self.devicePixelRatio = devicePixelRatio
+
+
+    def setFilePath(self, path):
+        # Set where to save persistant data
+        self.credentialProvider.setPath(path)
+
+    def _getAppVersion(self):
+        return self.appVersion
+
+    def _getCapabilities(self):
+        return self.capabilities
+
+    def _getDeviceId(self):
+        return self.deviceId
+
+    def _connectUserId(self):
+        return self.credentialProvider.getCredentials().get('ConnectUserId')
+
+    def _connectToken(self):
+        return self.credentialProvider.getCredentials().get('ConnectAccessToken')
+
+    def getServerInfo(self, id_):
+
+        servers = self.credentialProvider.getCredentials()['Servers']
+        
+        for s in servers:
+            if s['Id'] == id_:
+                return s
+
+    def _getLastUsedServer(self):
+
+        servers = self.credentialProvider.getCredentials()['Servers']
+
+        if not len(servers):
+            return
+
+        try:
+            servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True)
+        except TypeError:
+            servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True)
+
+        return servers[0]
+
+    def _mergeServers(self, list1, list2):
+
+        for i in range(0, len(list2), 1):
+            try:
+                self.credentialProvider.addOrUpdateServer(list1, list2[i])
+            except KeyError:
+                continue
+
+        return list1
+
+    def _connectUser(self):
+        
+        return self.connectUser
+
+    def _resolveFailure(self):
+
+        return {
+            'State': ConnectionState['Unavailable'],
+            'ConnectUser': self._connectUser()
+        }
+
+    def _getMinServerVersion(self, val=None):
+
+        if val is not None:
+            self.minServerVersion = val
+
+        return self.minServerVersion
+
+    def _updateServerInfo(self, server, systemInfo):
+
+        server['Name'] = systemInfo['ServerName']
+        server['Id'] = systemInfo['Id']
+
+        if systemInfo.get('LocalAddress'):
+            server['LocalAddress'] = systemInfo['LocalAddress']
+        if systemInfo.get('WanAddress'):
+            server['RemoteAddress'] = systemInfo['WanAddress']
+        if systemInfo.get('MacAddress'):
+            server['WakeOnLanInfos'] = [{'MacAddress': systemInfo['MacAddress']}]
+
+    def _getHeaders(self, request):
+        
+        headers = request.setdefault('headers', {})
+
+        if request.get('dataType') == "json":
+            headers['Accept'] = "application/json"
+            request.pop('dataType')
+
+        headers['X-Application'] = self._addAppInfoToConnectRequest()
+        headers['Content-type'] = request.get('contentType',
+            'application/x-www-form-urlencoded; charset=UTF-8')
+
+    def requestUrl(self, request):
+
+        if not request:
+            raise AttributeError("Request cannot be null")
+
+        self._getHeaders(request)
+        request['timeout'] = request.get('timeout') or self.default_timeout
+        request['verify'] = False
+
+        action = request['type']
+        request.pop('type')
+
+        log.debug("ConnectionManager requesting %s" % request)
+
+        try:
+            r = self._requests(action, **request)
+            log.info("ConnectionManager response status: %s" % r.status_code)
+            r.raise_for_status()
+        
+        except Exception as e: # Elaborate on exceptions?
+            log.error(e)
+            raise
+
+        else:
+            try:
+                return r.json()
+            except ValueError:
+                r.content # Read response to release connection
+                return
+
+    def _requests(self, action, **kwargs):
+
+        if action == "GET":
+            r = requests.get(**kwargs)
+        elif action == "POST":
+            r = requests.post(**kwargs)
+
+        return r
+
+    def getEmbyServerUrl(self, baseUrl, handler):
+        return "%s/emby/%s" % (baseUrl, handler)
+
+    def getConnectUrl(self, handler):
+        return "https://connect.emby.media/service/%s" % handler
+
+    def _findServers(self, foundServers):
+
+        servers = []
+
+        for foundServer in foundServers:
+
+            server = self._convertEndpointAddressToManualAddress(foundServer)
+
+            info = {
+                'Id': foundServer['Id'],
+                'LocalAddress': server or foundServer['Address'],
+                'Name': foundServer['Name']
+            }
+            info['LastConnectionMode'] = ConnectionMode['Manual'] if info.get('ManualAddress') else ConnectionMode['Local']
+            
+            servers.append(info)
+        else:
+            return servers
+
+    def _convertEndpointAddressToManualAddress(self, info):
+        
+        if info.get('Address') and info.get('EndpointAddress'):
+            address = info['EndpointAddress'].split(':')[0]
+
+            # Determine the port, if any
+            parts = info['Address'].split(':')
+            if len(parts) > 1:
+                portString = parts[len(parts)-1]
+
+                try:
+                    address += ":%s" % int(portString)
+                    return self._normalizeAddress(address)
+                except ValueError:
+                    pass
+
+        return None
+
+    def _serverDiscovery(self):
+        
+        MULTI_GROUP = ("<broadcast>", 7359)
+        MESSAGE = "who is EmbyServer?"
+        
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        sock.settimeout(1.0) # This controls the socket.timeout exception
+
+        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+        sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
+        sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
+        
+        log.debug("MultiGroup      : %s" % str(MULTI_GROUP))
+        log.debug("Sending UDP Data: %s" % MESSAGE)
+        sock.sendto(MESSAGE, MULTI_GROUP)
+        
+        servers = []
+        while True:
+            try:
+                data, addr = sock.recvfrom(1024) # buffer size
+                servers.append(json.loads(data))
+            
+            except socket.timeout:
+                log.info("Found Servers: %s" % servers)
+                return servers
+            
+            except Exception as e:
+                log.error("Error trying to find servers: %s" % e)
+                return servers
+
+    def _normalizeAddress(self, address):
+        # Attempt to correct bad input
+        address = address.strip()
+        address = address.lower()
+
+        if 'http' not in address:
+            address = "http://%s" % address
+
+        return address
+
+    def connectToAddress(self, address, options={}):
+
+        if not address:
+            return False
+
+        address = self._normalizeAddress(address)
+
+        def _onFail():
+            log.error("connectToAddress %s failed" % address)
+            return self._resolveFailure()
+
+        try:
+            publicInfo = self._tryConnect(address)
+        except Exception:
+            return _onFail()
+        else:
+            log.info("connectToAddress %s succeeded" % address)
+            server = {
+                'ManualAddress': address,
+                'LastConnectionMode': ConnectionMode['Manual']
+            }
+            self._updateServerInfo(server, publicInfo)
+            server = self.connectToServer(server, options)
+            if server is False:
+                return _onFail()
+            else:
+                return server
+
+    def _tryConnect(self, url, timeout=None):
+
+        url = self.getEmbyServerUrl(url, "system/info/public")
+        log.info("tryConnect url: %s" % url)
+
+        return self.requestUrl({
+            
+            'type': "GET",
+            'url': url,
+            'dataType': "json",
+            'timeout': timeout
+        })
+
+    def _addAppInfoToConnectRequest(self):
+        return "%s/%s" % (self.appName, self.appVersion)
+
+    def _getConnectServers(self, credentials):
+
+        log.info("Begin getConnectServers")
+        
+        servers = []
+
+        if not credentials.get('ConnectAccessToken') or not credentials.get('ConnectUserId'):
+            return servers
+
+        url = self.getConnectUrl("servers?userId=%s" % credentials['ConnectUserId'])
+        request = {
+
+            'type': "GET",
+            'url': url,
+            'dataType': "json",
+            'headers': {
+                'X-Connect-UserToken': credentials['ConnectAccessToken']
+            }
+        }
+        for server in self.requestUrl(request):
+
+            servers.append({
+
+                'ExchangeToken': server['AccessKey'],
+                'ConnectServerId': server['Id'],
+                'Id': server['SystemId'],
+                'Name': server['Name'],
+                'RemoteAddress': server['Url'],
+                'LocalAddress': server['LocalAddress'],
+                'UserLinkType': "Guest" if server['UserType'].lower() == "guest" else "LinkedUser",
+            })
+
+        return servers
+
+    def _getAvailableServers(self):
+        
+        log.info("Begin getAvailableServers")
+
+        # Clone the array
+        credentials = self.credentialProvider.getCredentials()
+
+        connectServers = self._getConnectServers(credentials)
+        foundServers = self._findServers(self._serverDiscovery())
+
+        servers = list(credentials['Servers'])
+        self._mergeServers(servers, foundServers)
+        self._mergeServers(servers, connectServers)
+
+        servers = self._filterServers(servers, connectServers)
+
+        try:
+            servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True)
+        except TypeError:
+            servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True)
+
+        credentials['Servers'] = servers
+        self.credentialProvider.getCredentials(credentials)
+
+        return servers
+
+    def _filterServers(self, servers, connectServers):
+        
+        filtered = []
+
+        for server in servers:
+            # It's not a connect server, so assume it's still valid
+            if server.get('ExchangeToken') is None:
+                filtered.append(server)
+                continue
+
+            for connectServer in connectServers:
+                if server['Id'] == connectServer['Id']:
+                    filtered.append(server)
+                    break
+        else:
+            return filtered
+
+    def _getConnectPasswordHash(self, password):
+
+        password = self._cleanConnectPassword(password)
+        
+        return hashlib.md5(password).hexdigest()
+
+    def _saveUserInfoIntoCredentials(self, server, user):
+
+        info = {
+            'Id': user['Id'],
+            'IsSignedInOffline': True
+        }
+
+        self.credentialProvider.addOrUpdateUser(server, info)
+
+    def _compareVersions(self, a, b):
+        """
+            -1 a is smaller
+            1 a is larger
+            0 equal
+        """
+        a = a.split('.')
+        b = b.split('.')
+
+        for i in range(0, max(len(a), len(b)), 1):
+            try:
+                aVal = a[i]
+            except IndexError:
+                aVal = 0
+
+            try:    
+                bVal = b[i]
+            except IndexError:
+                bVal = 0
+
+            if aVal < bVal:
+                return -1
+
+            if aVal > bVal:
+                return 1
+
+        return 0
+
+    def connectToServer(self, server, options={}):
+
+        log.info("begin connectToServer")
+
+        tests = []
+
+        if server.get('LastConnectionMode') is not None:
+            #tests.append(server['LastConnectionMode'])
+            pass
+        if ConnectionMode['Manual'] not in tests:
+            tests.append(ConnectionMode['Manual'])
+        if ConnectionMode['Local'] not in tests:
+            tests.append(ConnectionMode['Local'])
+        if ConnectionMode['Remote'] not in tests:
+            tests.append(ConnectionMode['Remote'])
+
+        # TODO: begin to wake server
+
+        log.info("beginning connection tests")
+        return self._testNextConnectionMode(tests, 0, server, options)
+
+    def _stringEqualsIgnoreCase(self, str1, str2):
+
+        return (str1 or "").lower() == (str2 or "").lower()
+
+    def _testNextConnectionMode(self, tests, index, server, options):
+
+        if index >= len(tests):
+            log.info("Tested all connection modes. Failing server connection.")
+            return self._resolveFailure()
+
+        mode = tests[index]
+        address = getServerAddress(server, mode)
+        enableRetry = False
+        skipTest = False
+        timeout = self.default_timeout
+
+        if mode == ConnectionMode['Local']:
+            enableRetry = True
+            timeout = 8
+
+            if self._stringEqualsIgnoreCase(address, server.get('ManualAddress')):
+                log.info("skipping LocalAddress test because it is the same as ManualAddress")
+                skipTest = True
+
+        elif mode == ConnectionMode['Manual']:
+
+            if self._stringEqualsIgnoreCase(address, server.get('LocalAddress')):
+                enableRetry = True
+                timeout = 8
+
+        if skipTest or not address:
+            log.info("skipping test at index: %s" % index)
+            return self._testNextConnectionMode(tests, index+1, server, options)
+
+        log.info("testing connection mode %s with server %s" % (mode, server['Name']))
+        try:
+            result = self._tryConnect(address, timeout)
+        
+        except Exception:
+            log.error("test failed for connection mode %s with server %s" % (mode, server['Name']))
+
+            if enableRetry:
+                # TODO: wake on lan and retry
+                return self._testNextConnectionMode(tests, index+1, server, options)
+            else:
+                return self._testNextConnectionMode(tests, index+1, server, options)
+        else:
+
+            if self._compareVersions(self._getMinServerVersion(), result['Version']) == 1:
+                log.warn("minServerVersion requirement not met. Server version: %s" % result['Version'])
+                return {
+                    'State': ConnectionState['ServerUpdateNeeded'],
+                    'Servers': [server]
+                }
+            else:
+                log.info("calling onSuccessfulConnection with connection mode %s with server %s"
+                        % (mode, server['Name']))
+                return self._onSuccessfulConnection(server, result, mode, options)
+
+    def _onSuccessfulConnection(self, server, systemInfo, connectionMode, options):
+
+        credentials = self.credentialProvider.getCredentials()
+
+        if credentials.get('ConnectAccessToken') and options.get('enableAutoLogin') is not False:
+            
+            if self._ensureConnectUser(credentials) is not False:
+
+                if server.get('ExchangeToken'):
+                    
+                    self._addAuthenticationInfoFromConnect(server, connectionMode, credentials)
+
+        return self._afterConnectValidated(server, credentials, systemInfo, connectionMode, True, options)
+
+    def _afterConnectValidated(self, server, credentials, systemInfo, connectionMode, verifyLocalAuthentication, options):
+
+        if options.get('enableAutoLogin') is False:
+            server['UserId'] = None
+            server['AccessToken'] = None
+        
+        elif (verifyLocalAuthentication and server.get('AccessToken') and 
+            options.get('enableAutoLogin') is not False):
+
+            if self._validateAuthentication(server, connectionMode) is not False:
+                return self._afterConnectValidated(server, credentials, systemInfo, connectionMode, False, options)
+
+            return
+
+        self._updateServerInfo(server, systemInfo)
+        server['LastConnectionMode'] = connectionMode
+
+        if options.get('updateDateLastAccessed') is not False:
+            server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
+
+        self.credentialProvider.addOrUpdateServer(credentials['Servers'], server)
+        self.credentialProvider.getCredentials(credentials)
+
+        result = {
+            'Servers': [],
+            'ConnectUser': self._connectUser()
+        }
+        result['State'] = ConnectionState['SignedIn'] if (server.get('AccessToken') and options.get('enableAutoLogin') is not False) else ConnectionState['ServerSignIn']
+        result['Servers'].append(server)
+
+        # Connected
+        return result
+
+    def _validateAuthentication(self, server, connectionMode):
+
+        url = getServerAddress(server, connectionMode)
+        request = {
+
+            'type': "GET",
+            'url': self.getEmbyServerUrl(url, "System/Info"),
+            'dataType': "json",
+            'headers': {
+                'X-MediaBrowser-Token': server['AccessToken']
+            }
+        }
+        try:
+            systemInfo = self.requestUrl(request)
+            self._updateServerInfo(server, systemInfo)
+
+            if server.get('UserId'):
+                user = self.requestUrl({
+
+                    'type': "GET",
+                    'url': self.getEmbyServerUrl(url, "users/%s" % server['UserId']),
+                    'dataType': "json",
+                    'headers': {
+                        'X-MediaBrowser-Token': server['AccessToken']
+                    }
+                })
+                # TODO: add apiclient
+
+        except Exception:
+            server['UserId'] = None
+            server['AccessToken'] = None
+            return False
+
+    def loginToConnect(self, username, password):
+
+        if not username:
+            raise AttributeError("username cannot be empty")
+
+        if not password:
+            raise AttributeError("password cannot be empty")
+
+        md5 = self._getConnectPasswordHash(password)
+        request = {
+            'type': "POST",
+            'url': self.getConnectUrl("user/authenticate"),
+            'data': {
+                'nameOrEmail': username,
+                'password': md5
+            },
+            'dataType': "json"
+        }
+        try:
+            result = self.requestUrl(request)
+        except Exception as e: # Failed to login
+            log.error(e)
+            return False
+        else:
+            credentials = self.credentialProvider.getCredentials()
+            credentials['ConnectAccessToken'] = result['AccessToken']
+            credentials['ConnectUserId'] = result['User']['Id']
+            self.credentialProvider.getCredentials(credentials)
+            # Signed in
+            self._onConnectUserSignIn(result['User'])
+        
+        return result
+
+    def onAuthenticated(self, result, options={}):
+
+        credentials = self.credentialProvider.getCredentials()
+        for s in credentials['Servers']:
+            if s['Id'] == result['ServerId']:
+                server = s
+                break
+        else: # Server not found?
+            return
+
+        if options.get('updateDateLastAccessed') is not False:
+            server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
+
+        server['UserId'] = result['User']['Id']
+        server['AccessToken'] = result['AccessToken']
+
+        self.credentialProvider.addOrUpdateServer(credentials['Servers'], server)
+        self._saveUserInfoIntoCredentials(server, result['User'])
+        self.credentialProvider.getCredentials(credentials)
+
+    def _onConnectUserSignIn(self, user):
+
+        self.connectUser = user
+        log.info("connectusersignedin %s" % user)
+
+    def _getConnectUser(self, userId, accessToken):
+
+        if not userId:
+            raise AttributeError("null userId")
+
+        if not accessToken:
+            raise AttributeError("null accessToken")
+
+        url = self.getConnectUrl('user?id=%s' % userId)
+
+        return self.requestUrl({
+            
+            'type': "GET",
+            'url': url,
+            'dataType': "json",
+            'headers': {
+                'X-Connect-UserToken': accessToken
+            }
+        })
+
+    def _addAuthenticationInfoFromConnect(self, server, connectionMode, credentials):
+
+        if not server.get('ExchangeToken'):
+            raise KeyError("server['ExchangeToken'] cannot be null")
+
+        if not credentials.get('ConnectUserId'):
+            raise KeyError("credentials['ConnectUserId'] cannot be null")
+
+        url = getServerAddress(server, connectionMode)
+        url = self.getEmbyServerUrl(url, "Connect/Exchange?format=json")
+        try:
+            auth = self.requestUrl({
+
+                'url': url,
+                'type': "GET",
+                'dataType': "json",
+                'params': {
+                    'ConnectUserId': credentials['ConnectUserId']
+                },
+                'headers': {
+                    'X-MediaBrowser-Token': server['ExchangeToken']
+                }
+            })
+        except Exception:
+            server['UserId'] = None
+            server['AccessToken'] = None
+            return False
+        else:
+            server['UserId'] = auth['LocalUserId']
+            server['AccessToken'] = auth['AccessToken']
+            return auth
+
+    def _ensureConnectUser(self, credentials):
+
+        if self.connectUser and self.connectUser['Id'] == credentials['ConnectUserId']:
+            return
+
+        elif credentials.get('ConnectUserId') and credentials.get('ConnectAccessToken'):
+
+            self.connectUser = None
+
+            try:
+                result = self._getConnectUser(credentials['ConnectUserId'], credentials['ConnectAccessToken'])
+                self._onConnectUserSignIn(result)
+            except Exception:
+                return False
+
+    def connect(self, options={}):
+
+        log.info("Begin connect")
+
+        servers = self._getAvailableServers()
+        return self._connectToServers(servers, options)
+
+    def _connectToServers(self, servers, options):
+
+        log.info("Begin connectToServers, with %s servers" % len(servers))
+
+        if len(servers) == 1:
+            result = self.connectToServer(servers[0], options)
+            if result.get('State') == ConnectionState['Unavailable']:
+                result['State'] = ConnectionState['ConnectSignIn'] if result['ConnectUser'] == None else ConnectionState['ServerSelection']
+
+            log.info("resolving connectToServers with result['State']: %s" % result)
+            return result
+
+        firstServer = self._getLastUsedServer()
+        # See if we have any saved credentials and can auto sign in
+        if firstServer:
+            
+            result = self.connectToServer(firstServer, options)
+            if result and result.get('State') == ConnectionState['SignedIn']:
+                return result
+
+        # Return loaded credentials if exists
+        credentials = self.credentialProvider.getCredentials()
+        self._ensureConnectUser(credentials)
+
+        return {
+            'Servers': servers,
+            'State': ConnectionState['ConnectSignIn'] if (not len(servers) and not self._connectUser()) else ConnectionState['ServerSelection'],
+            'ConnectUser': self._connectUser()
+        }
+
+    def _cleanConnectPassword(self, password):
+
+        password = password or ""
+
+        password = password.replace("&", '&amp;')
+        password = password.replace("/", '&#092;')
+        password = password.replace("!", '&#33;')
+        password = password.replace("$", '&#036;')
+        password = password.replace("\"", '&quot;')
+        password = password.replace("<", '&lt;')
+        password = password.replace(">", '&gt;')
+        password = password.replace("'", '&#39;')
+
+        return password
+
+    def clearData(self):
+
+        log.info("connection manager clearing data")
+
+        self.connectUser = None
+        credentials = self.credentialProvider.getCredentials()
+        credentials['ConnectAccessToken'] = None
+        credentials['ConnectUserId'] = None
+        credentials['Servers'] = []
+        self.credentialProvider.getCredentials(credentials)
\ No newline at end of file
diff --git a/resources/lib/connect/credentials.py b/resources/lib/connect/credentials.py
new file mode 100644
index 00000000..9cc779b6
--- /dev/null
+++ b/resources/lib/connect/credentials.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import logging
+import os
+import time
+from datetime import datetime
+
+#################################################################################################
+
+log = logging.getLogger("EMBY."+__name__.split('.')[-1])
+
+#################################################################################################
+
+
+class Credentials(object):
+
+    credentials = None
+    path = ""
+    
+
+    def __init__(self):
+        pass
+
+    def setPath(self, path):
+        # Path to save persistant data.txt
+        self.path = path
+
+    def _ensure(self):
+        
+        if self.credentials is None:
+            try:
+                with open(os.path.join(self.path, 'data.txt')) as infile:
+                    self.credentials = json.load(infile)
+            
+            except Exception as e: # File is either empty or missing
+                log.warn(e)
+                self.credentials = {}
+            
+            log.info("credentials initialized with: %s" % self.credentials)
+            self.credentials['Servers'] = self.credentials.setdefault('Servers', [])
+
+    def _get(self):
+
+        self._ensure()
+        return self.credentials
+
+    def _set(self, data):
+
+        if data:
+            self.credentials = data
+            # Set credentials to file
+            with open(os.path.join(self.path, 'data.txt'), 'w') as outfile:
+                json.dump(data, outfile, indent=4, ensure_ascii=False)
+        else:
+            self._clear()
+
+        log.info("credentialsupdated")
+
+    def _clear(self):
+
+        self.credentials = None
+        # Remove credentials from file
+        with open(os.path.join(self.path, 'data.txt'), 'w'): pass
+
+    def getCredentials(self, data=None):
+
+        if data is not None:
+            self._set(data)
+
+        return self._get()
+
+    def addOrUpdateServer(self, list_, server):
+
+        if server.get('Id') is None:
+            raise KeyError("Server['Id'] cannot be null or empty")
+
+        # Add default DateLastAccessed if doesn't exist.
+        server.setdefault('DateLastAccessed', "2001-01-01T00:00:00Z")
+
+        for existing in list_:
+            if existing['Id'] == server['Id']:
+                
+                # Merge the data
+                if server.get('DateLastAccessed'):
+                    if self._dateObject(server['DateLastAccessed']) > self._dateObject(existing['DateLastAccessed']):
+                        existing['DateLastAccessed'] = server['DateLastAccessed']
+
+                if server.get('UserLinkType'):
+                    existing['UserLinkType'] = server['UserLinkType']
+
+                if server.get('AccessToken'):
+                    existing['AccessToken'] = server['AccessToken']
+                    existing['UserId'] = server['UserId']
+
+                if server.get('ExchangeToken'):
+                    existing['ExchangeToken'] = server['ExchangeToken']
+
+                if server.get('RemoteAddress'):
+                    existing['RemoteAddress'] = server['RemoteAddress']
+
+                if server.get('ManualAddress'):
+                    existing['ManualAddress'] = server['ManualAddress']
+
+                if server.get('LocalAddress'):
+                    existing['LocalAddress'] = server['LocalAddress']
+
+                if server.get('Name'):
+                    existing['Name'] = server['Name']
+
+                if server.get('WakeOnLanInfos'):
+                    existing['WakeOnLanInfos'] = server['WakeOnLanInfos']
+
+                if server.get('LastConnectionMode') is not None:
+                    existing['LastConnectionMode'] = server['LastConnectionMode']
+
+                if server.get('ConnectServerId'):
+                    existing['ConnectServerId'] = server['ConnectServerId']
+
+                return existing
+        else:
+            list_.append(server)
+            return server
+
+    def addOrUpdateUser(self, server, user):
+
+        for existing in server.setdefault('Users', []):
+            if existing['Id'] == user['Id']:
+                # Merge the data
+                existing['IsSignedInOffline'] = True
+                break
+        else:
+            server['Users'].append(user)
+
+    def _dateObject(self, date):
+        # Convert string to date
+        try:
+            date_obj = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
+        except (ImportError, TypeError):
+            # TypeError: attribute of type 'NoneType' is not callable
+            # Known Kodi/python error
+            date_obj = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
+        
+        return date_obj
\ No newline at end of file
diff --git a/resources/lib/connectmanager.py b/resources/lib/connectmanager.py
new file mode 100644
index 00000000..22afc2eb
--- /dev/null
+++ b/resources/lib/connectmanager.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import logging
+
+import xbmc
+import xbmcaddon
+
+import clientinfo
+import read_embyserver as embyserver
+import connect.connectionmanager as connectionmanager
+from dialog import ServerConnect, UsersConnect, LoginConnect, LoginManual, ServerManual
+
+##################################################################################################
+
+log = logging.getLogger("EMBY."+__name__)
+addon = xbmcaddon.Addon(id='plugin.video.emby')
+
+XML_PATH = (addon.getAddonInfo('path'), "default", "1080i")
+
+##################################################################################################
+
+class ConnectManager(object):
+
+    _shared_state = {} # Borg
+    state = {}
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+
+        if not self.state:
+            client_info = clientinfo.ClientInfo()
+            self.emby = embyserver.Read_EmbyServer()
+
+            version = client_info.getVersion()
+            device_name = client_info.getDeviceName()
+            device_id = client_info.getDeviceId()
+
+            self._connect = connectionmanager.ConnectionManager(appName="Kodi",
+                                                                appVersion=version,
+                                                                deviceName=device_name,
+                                                                deviceId=device_id)
+            self._connect.setFilePath(xbmc.translatePath(
+                                      addon.getAddonInfo('profile')).decode('utf-8'))
+            self.state = self._connect.connect()
+            log.info("Started with: %s", self.state)
+
+
+    def update_state(self):
+
+        self.state = self._connect.connect({'updateDateLastAccessed': False})
+        return self.state
+
+    def get_state(self):
+        return self.state
+
+    def get_server(self, server):
+        return self._connect.connectToAddress(server)
+
+    @classmethod
+    def get_address(cls, server):
+        return connectionmanager.getServerAddress(server, server['LastConnectionMode'])
+
+    def clear_data(self):
+        self._connect.clearData()
+
+    def select_servers(self):
+        # Will return selected server or raise error
+        state = self._connect.connect({'enableAutoLogin': False})
+        user = state.get('ConnectUser') or {}
+
+        dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH)
+        kwargs = {
+            'connect_manager': self._connect,
+            'username': user.get('DisplayName', ""),
+            'user_image': user.get('ImageUrl'),
+            'servers': state['Servers'],
+            'emby_connect': False if user else True
+        }
+        dialog.set_args(**kwargs)
+        dialog.doModal()
+
+        if dialog.is_server_selected():
+            log.debug("Server selected")
+            return dialog.get_server()
+
+        elif dialog.is_connect_login():
+            log.debug("Login with Emby Connect")
+            try: # Login to emby connect
+                self.login_connect()
+            except RuntimeError:
+                pass
+            return self.select_servers()
+
+        elif dialog.is_manual_server():
+            log.debug("Add manual server")
+            try: # Add manual server address
+                return self.manual_server()
+            except RuntimeError:
+                return self.select_servers()
+        else:
+            raise RuntimeError("No server selected")
+
+    def manual_server(self):
+        # Return server or raise error
+        dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH)
+        dialog.set_connect_manager(self._connect)
+        dialog.doModal()
+
+        if dialog.is_connected():
+            return dialog.get_server()
+        else:
+            raise RuntimeError("Server is not connected")
+
+    def login_connect(self):
+        # Return connect user or raise error
+        dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH)
+        dialog.set_connect_manager(self._connect)
+        dialog.doModal()
+
+        self.update_state()
+
+        if dialog.is_logged_in():
+            return dialog.get_user()
+        else:
+            raise RuntimeError("Connect user is not logged in")
+
+    def login(self, server=None):
+        # Return user or raise error
+        server = server or self.state['Servers'][0]
+        server_address = connectionmanager.getServerAddress(server, server['LastConnectionMode'])
+        users = self.emby.getUsers(server_address)
+
+        dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH)
+        dialog.set_server(server_address)
+        dialog.set_users(users)
+        dialog.doModal()
+
+        if dialog.is_user_selected():
+            user = dialog.get_user()
+            if user['HasPassword']:
+                log.debug("User has password, present manual login")
+                try:
+                    return self.login_manual(server_address, user)
+                except RuntimeError:
+                    return self.login(server)
+            else:
+                user = self.emby.loginUser(server_address, user['Name'])
+                self._connect.onAuthenticated(user)
+                return user
+
+        elif dialog.is_manual_login():
+            try:
+                return self.login_manual(server_address)
+            except RuntimeError:
+                return self.login(server)
+        else:
+            raise RuntimeError("No user selected")
+
+    def login_manual(self, server, user=None):
+        # Return manual login user authenticated or raise error
+        dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH)
+        dialog.set_server(server)
+        dialog.set_user(user)
+        dialog.doModal()
+
+        if dialog.is_logged_in():
+            user = dialog.get_user()
+            self._connect.onAuthenticated(user)
+            return user
+        else:
+            raise RuntimeError("User is not authenticated")
diff --git a/resources/lib/dialog/__init__.py b/resources/lib/dialog/__init__.py
index b93054b3..b6c69bf6 100644
--- a/resources/lib/dialog/__init__.py
+++ b/resources/lib/dialog/__init__.py
@@ -1 +1,6 @@
 # Dummy file to make this directory a package.
+from serverconnect import ServerConnect
+from usersconnect import UsersConnect
+from loginconnect import LoginConnect
+from loginmanual import LoginManual
+from servermanual import ServerManual
diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py
index 62651e4f..db7c39cc 100644
--- a/resources/lib/dialog/loginconnect.py
+++ b/resources/lib/dialog/loginconnect.py
@@ -2,52 +2,63 @@
 
 ##################################################################################################
 
+import logging
 import os
 
 import xbmcgui
 import xbmcaddon
 
+from utils import language as lang
+
 ##################################################################################################
 
+log = logging.getLogger("EMBY."+__name__)
 addon = xbmcaddon.Addon('plugin.video.emby')
 
+ACTION_PARENT_DIR = 9
+ACTION_PREVIOUS_MENU = 10
 ACTION_BACK = 92
 SIGN_IN = 200
-REMIND_LATER = 201
+CANCEL = 201
+ERROR_TOGGLE = 202
+ERROR_MSG = 203
+ERROR = {
+    'Invalid': 1,
+    'Empty': 2
+}
+
+##################################################################################################
 
 
 class LoginConnect(xbmcgui.WindowXMLDialog):
 
+    _user = None
+    error = None
+
 
     def __init__(self, *args, **kwargs):
 
         xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
 
-    def __add_editcontrol(self, x, y, height, width, password=0):
-        
-        media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
-        control = xbmcgui.ControlEdit(0,0,0,0,
-                            label="User",
-                            font="font10",
-                            textColor="ff464646",
-                            focusTexture=os.path.join(media, "button-focus.png"),
-                            noFocusTexture=os.path.join(media, "button-focus.png"),
-                            isPassword=password)
+    def set_connect_manager(self, connect_manager):
+        self.connect_manager = connect_manager
 
-        control.setPosition(x,y)
-        control.setHeight(height)
-        control.setWidth(width)
+    def is_logged_in(self):
+        return True if self._user else False
+
+    def get_user(self):
+        return self._user
 
-        self.addControl(control)
-        return control
 
     def onInit(self):
-        
-        self.user_field = self.__add_editcontrol(685,385,40,500)
+
+        self.user_field = self._add_editcontrol(725, 385, 40, 500)
         self.setFocus(self.user_field)
-        self.password_field = self.__add_editcontrol(685,470,40,500, password=1)
+        self.password_field = self._add_editcontrol(725, 470, 40, 500, password=1)
         self.signin_button = self.getControl(SIGN_IN)
-        self.remind_button = self.getControl(REMIND_LATER)
+        self.remind_button = self.getControl(CANCEL)
+        self.error_toggle = self.getControl(ERROR_TOGGLE)
+        self.error_msg = self.getControl(ERROR_MSG)
 
         self.user_field.controlUp(self.remind_button)
         self.user_field.controlDown(self.password_field)
@@ -60,17 +71,66 @@ class LoginConnect(xbmcgui.WindowXMLDialog):
 
         if control == SIGN_IN:
             # Sign in to emby connect
-            self.user = self.user_field.getText()
-            __password = self.password_field.getText()
+            self._disable_error()
 
-            ### REVIEW ONCE CONNECT MODULE IS MADE
-            self.close()
+            user = self.user_field.getText()
+            password = self.password_field.getText()
 
-        elif control == REMIND_LATER:
+            if not user or not password:
+                # Display error
+                self._error(ERROR['Empty'], lang(30608))
+                log.error("Username or password cannot be null")
+
+            elif self._login(user, password):
+                self.close()
+
+        elif control == CANCEL:
             # Remind me later
             self.close()
 
     def onAction(self, action):
 
-        if action == ACTION_BACK:
-            self.close()
\ No newline at end of file
+        if (self.error == ERROR['Empty']
+                and self.user_field.getText() and self.password_field.getText()):
+            self._disable_error()
+
+        if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
+            self.close()
+
+    def _add_editcontrol(self, x, y, height, width, password=0):
+
+        media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
+        control = xbmcgui.ControlEdit(0, 0, 0, 0,
+                                      label="User",
+                                      font="font10",
+                                      textColor="ff525252",
+                                      focusTexture=os.path.join(media, "button-focus.png"),
+                                      noFocusTexture=os.path.join(media, "button-focus.png"),
+                                      isPassword=password)
+        control.setPosition(x, y)
+        control.setHeight(height)
+        control.setWidth(width)
+
+        self.addControl(control)
+        return control
+
+    def _login(self, username, password):
+
+        result = self.connect_manager.loginToConnect(username, password)
+        if result is False:
+            self._error(ERROR['Invalid'], lang(33009))
+            return False
+        else:
+            self._user = result
+            return True
+
+    def _error(self, state, message):
+
+        self.error = state
+        self.error_msg.setLabel(message)
+        self.error_toggle.setVisibleCondition('True')
+
+    def _disable_error(self):
+
+        self.error = None
+        self.error_toggle.setVisibleCondition('False')
diff --git a/resources/lib/dialog/loginmanual.py b/resources/lib/dialog/loginmanual.py
new file mode 100644
index 00000000..aa34986b
--- /dev/null
+++ b/resources/lib/dialog/loginmanual.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import logging
+import os
+
+import xbmcgui
+import xbmcaddon
+
+import read_embyserver as embyserver
+from utils import language as lang
+
+##################################################################################################
+
+log = logging.getLogger("EMBY."+__name__)
+addon = xbmcaddon.Addon('plugin.video.emby')
+
+ACTION_PARENT_DIR = 9
+ACTION_PREVIOUS_MENU = 10
+ACTION_BACK = 92
+SIGN_IN = 200
+CANCEL = 201
+ERROR_TOGGLE = 202
+ERROR_MSG = 203
+ERROR = {
+    'Invalid': 1,
+    'Empty': 2
+}
+
+##################################################################################################
+
+
+class LoginManual(xbmcgui.WindowXMLDialog):
+
+    _user = None
+    error = None
+
+
+    def __init__(self, *args, **kwargs):
+
+        self.emby = embyserver.Read_EmbyServer()
+        xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
+
+    def is_logged_in(self):
+        return True if self._user else False
+
+    def set_server(self, server):
+        self.server = server
+
+    def set_user(self, user):
+        self.user = user or {}
+
+    def get_user(self):
+        return self._user
+
+    def onInit(self):
+
+        self.signin_button = self.getControl(SIGN_IN)
+        self.cancel_button = self.getControl(CANCEL)
+        self.error_toggle = self.getControl(ERROR_TOGGLE)
+        self.error_msg = self.getControl(ERROR_MSG)
+        self.user_field = self._add_editcontrol(725, 400, 40, 500)
+        self.password_field = self._add_editcontrol(725, 475, 40, 500, password=1)
+
+        if "Name" in self.user:
+            self.user_field.setText(self.user['Name'])
+            self.setFocus(self.password_field)
+        else:
+            self.setFocus(self.user_field)
+
+        self.user_field.controlUp(self.cancel_button)
+        self.user_field.controlDown(self.password_field)
+        self.password_field.controlUp(self.user_field)
+        self.password_field.controlDown(self.signin_button)
+        self.signin_button.controlUp(self.password_field)
+        self.cancel_button.controlDown(self.user_field)
+
+    def onClick(self, control):
+
+        if control == SIGN_IN:
+            # Sign in to emby connect
+            self._disable_error()
+
+            user = self.user_field.getText()
+            password = self.password_field.getText()
+
+            if not user:
+                # Display error
+                self._error(ERROR['Empty'], lang(30613))
+                log.error("Username cannot be null")
+
+            elif self._login(user, password):
+                self.close()
+
+        elif control == CANCEL:
+            # Remind me later
+            self.close()
+
+    def onAction(self, action):
+
+        if self.error == ERROR['Empty'] and self.user_field.getText():
+            self._disable_error()
+
+        if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
+            self.close()
+
+    def _add_editcontrol(self, x, y, height, width, password=0):
+
+        media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
+        control = xbmcgui.ControlEdit(0, 0, 0, 0,
+                                      label="User",
+                                      font="font10",
+                                      textColor="ff525252",
+                                      focusTexture=os.path.join(media, "button-focus.png"),
+                                      noFocusTexture=os.path.join(media, "button-focus.png"),
+                                      isPassword=password)
+        control.setPosition(x, y)
+        control.setHeight(height)
+        control.setWidth(width)
+
+        self.addControl(control)
+        return control
+
+    def _login(self, username, password):
+
+        result = self.emby.loginUser(self.server, username, password)
+        if not result:
+            self._error(ERROR['Invalid'], lang(33009))
+            return False
+        else:
+            self._user = result
+            return True
+
+    def _error(self, state, message):
+
+        self.error = state
+        self.error_msg.setLabel(message)
+        self.error_toggle.setVisibleCondition('True')
+
+    def _disable_error(self):
+
+        self.error = None
+        self.error_toggle.setVisibleCondition('False')
diff --git a/resources/lib/dialog/serverconnect.py b/resources/lib/dialog/serverconnect.py
new file mode 100644
index 00000000..8a39b09b
--- /dev/null
+++ b/resources/lib/dialog/serverconnect.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import logging
+
+import xbmc
+import xbmcgui
+
+import connect.connectionmanager as connectionmanager
+from utils import language as lang
+
+##################################################################################################
+
+log = logging.getLogger("EMBY."+__name__)
+
+CONN_STATE = connectionmanager.ConnectionState
+ACTION_PARENT_DIR = 9
+ACTION_PREVIOUS_MENU = 10
+ACTION_BACK = 92
+ACTION_SELECT_ITEM = 7
+ACTION_MOUSE_LEFT_CLICK = 100
+USER_IMAGE = 150
+USER_NAME = 151
+LIST = 155
+CANCEL = 201
+MESSAGE_BOX = 202
+MESSAGE = 203
+BUSY = 204
+EMBY_CONNECT = 205
+MANUAL_SERVER = 206
+
+##################################################################################################
+
+
+class ServerConnect(xbmcgui.WindowXMLDialog):
+
+    username = ""
+    user_image = None
+    servers = []
+
+    _selected_server = None
+    _connect_login = False
+    _manual_server = False
+
+
+    def __init__(self, *args, **kwargs):
+
+        xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
+
+    def set_args(self, **kwargs):
+        # connect_manager, username, user_image, servers, emby_connect
+        for key, value in kwargs.iteritems():
+            setattr(self, key, value)
+
+    def is_server_selected(self):
+        return True if self._selected_server else False
+
+    def get_server(self):
+        return self._selected_server
+
+    def is_connect_login(self):
+        return self._connect_login
+
+    def is_manual_server(self):
+        return self._manual_server
+
+
+    def onInit(self):
+
+        self.message = self.getControl(MESSAGE)
+        self.message_box = self.getControl(MESSAGE_BOX)
+        self.busy = self.getControl(BUSY)
+        self.list_ = self.getControl(LIST)
+
+        for server in self.servers:
+            server_type = "wifi" if server.get('ExchangeToken') else "network"
+            self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type))
+
+        self.getControl(USER_NAME).setLabel("%s %s" % (lang(33000), self.username.decode('utf-8')))
+
+        if self.user_image is not None:
+            self.getControl(USER_IMAGE).setImage(self.user_image)
+
+        if not self.emby_connect: # Change connect user
+            self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+lang(30618)+"[/B][/UPPERCASE]")
+
+        self.setFocus(self.list_)
+
+    @classmethod
+    def _add_listitem(cls, label, server_id, server_type):
+
+        item = xbmcgui.ListItem(label)
+        item.setProperty('id', server_id)
+        item.setProperty('server_type', server_type)
+
+        return item
+
+    def onAction(self, action):
+
+        if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR):
+            self.close()
+
+        if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK):
+
+            if self.getFocusId() == LIST:
+                server = self.list_.getSelectedItem()
+                selected_id = server.getProperty('id')
+                log.info('Server Id selected: %s', selected_id)
+
+                if self._connect_server(selected_id):
+                    self.message_box.setVisibleCondition('False')
+                    self.close()
+
+    def onClick(self, control):
+
+        if control == EMBY_CONNECT:
+            self.connect_manager.clearData()
+            self._connect_login = True
+            self.close()
+
+        elif control == MANUAL_SERVER:
+            self._manual_server = True
+            self.close()
+
+        elif control == CANCEL:
+            self.close()
+
+    def _connect_server(self, server_id):
+
+        server = self.connect_manager.getServerInfo(server_id)
+        self.message.setLabel("%s %s..." % (lang(30610), server['Name']))
+        self.message_box.setVisibleCondition('True')
+        self.busy.setVisibleCondition('True')
+        result = self.connect_manager.connectToServer(server)
+
+        if result['State'] == CONN_STATE['Unavailable']:
+            self.busy.setVisibleCondition('False')
+            self.message.setLabel(lang(30609))
+            return False
+        else:
+            xbmc.sleep(1000)
+            self._selected_server = result['Servers'][0]
+            return True
diff --git a/resources/lib/dialog/servermanual.py b/resources/lib/dialog/servermanual.py
new file mode 100644
index 00000000..d54199eb
--- /dev/null
+++ b/resources/lib/dialog/servermanual.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import logging
+import os
+
+import xbmcgui
+import xbmcaddon
+
+import connect.connectionmanager as connectionmanager
+from utils import language as lang
+
+##################################################################################################
+
+log = logging.getLogger("EMBY."+__name__)
+addon = xbmcaddon.Addon('plugin.video.emby')
+
+CONN_STATE = connectionmanager.ConnectionState
+ACTION_PARENT_DIR = 9
+ACTION_PREVIOUS_MENU = 10
+ACTION_BACK = 92
+CONNECT = 200
+CANCEL = 201
+ERROR_TOGGLE = 202
+ERROR_MSG = 203
+ERROR = {
+    'Invalid': 1,
+    'Empty': 2
+}
+
+##################################################################################################
+
+
+class ServerManual(xbmcgui.WindowXMLDialog):
+
+    _server = None
+    error = None
+
+
+    def __init__(self, *args, **kwargs):
+
+        xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
+
+    def set_connect_manager(self, connect_manager):
+        self.connect_manager = connect_manager
+
+    def is_connected(self):
+        return True if self._server else False
+
+    def get_server(self):
+        return self._server
+
+    def onInit(self):
+
+        self.connect_button = self.getControl(CONNECT)
+        self.cancel_button = self.getControl(CANCEL)
+        self.error_toggle = self.getControl(ERROR_TOGGLE)
+        self.error_msg = self.getControl(ERROR_MSG)
+        self.host_field = self._add_editcontrol(725, 400, 40, 500)
+        self.port_field = self._add_editcontrol(725, 525, 40, 500)
+
+        self.port_field.setText('8096')
+        self.setFocus(self.host_field)
+
+        self.host_field.controlUp(self.cancel_button)
+        self.host_field.controlDown(self.port_field)
+        self.port_field.controlUp(self.host_field)
+        self.port_field.controlDown(self.connect_button)
+        self.connect_button.controlUp(self.port_field)
+        self.cancel_button.controlDown(self.host_field)
+
+    def onClick(self, control):
+
+        if control == CONNECT:
+            # Sign in to emby connect
+            self._disable_error()
+
+            server = self.host_field.getText()
+            port = self.port_field.getText()
+
+            if not server or not port:
+                # Display error
+                self._error(ERROR['Empty'], lang(30617))
+                log.error("Server or port cannot be null")
+
+            elif self._connect_to_server(server, port):
+                self.close()
+
+        elif control == CANCEL:
+            # Remind me later
+            self.close()
+
+    def onAction(self, action):
+
+        if self.error == ERROR['Empty'] and self.host_field.getText() and self.port_field.getText():
+            self._disable_error()
+
+        if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
+            self.close()
+
+    def _add_editcontrol(self, x, y, height, width):
+
+        media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
+        control = xbmcgui.ControlEdit(0, 0, 0, 0,
+                                      label="User",
+                                      font="font10",
+                                      textColor="ffc2c2c2",
+                                      focusTexture=os.path.join(media, "button-focus.png"),
+                                      noFocusTexture=os.path.join(media, "button-focus.png"))
+        control.setPosition(x, y)
+        control.setHeight(height)
+        control.setWidth(width)
+
+        self.addControl(control)
+        return control
+
+    def _connect_to_server(self, server, port):
+
+        server_address = "%s:%s" % (server, port)
+        self._message("%s %s..." % (lang(30610), server_address))
+        result = self.connect_manager.connectToAddress(server_address)
+
+        if result['State'] == CONN_STATE['Unavailable']:
+            self._message(lang(30609))
+            return False
+        else:
+            self._server = result['Servers'][0]
+            return True
+
+    def _message(self, message):
+
+        self.error_msg.setLabel(message)
+        self.error_toggle.setVisibleCondition('True')
+
+    def _error(self, state, message):
+
+        self.error = state
+        self.error_msg.setLabel(message)
+        self.error_toggle.setVisibleCondition('True')
+
+    def _disable_error(self):
+
+        self.error = None
+        self.error_toggle.setVisibleCondition('False')
diff --git a/resources/lib/dialog/usersconnect.py b/resources/lib/dialog/usersconnect.py
new file mode 100644
index 00000000..314d2e23
--- /dev/null
+++ b/resources/lib/dialog/usersconnect.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import logging
+
+import xbmcgui
+
+##################################################################################################
+
+log = logging.getLogger("EMBY."+__name__)
+
+ACTION_PARENT_DIR = 9
+ACTION_PREVIOUS_MENU = 10
+ACTION_BACK = 92
+ACTION_SELECT_ITEM = 7
+ACTION_MOUSE_LEFT_CLICK = 100
+LIST = 155
+MANUAL = 200
+CANCEL = 201
+
+##################################################################################################
+
+
+class UsersConnect(xbmcgui.WindowXMLDialog):
+
+    _user = None
+    _manual_login = False
+
+
+    def __init__(self, *args, **kwargs):
+
+        xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
+
+    def set_server(self, server):
+        self.server = server
+
+    def set_users(self, users):
+        self.users = users
+
+    def is_user_selected(self):
+        return True if self._user else False
+
+    def get_user(self):
+        return self._user
+
+    def is_manual_login(self):
+        return self._manual_login
+
+
+    def onInit(self):
+
+        self.list_ = self.getControl(LIST)
+        for user in self.users:
+            user_image = ("userflyoutdefault2.png" if not user.get('PrimaryImageTag')
+                          else self._get_user_artwork(user['Id'], 'Primary'))
+            self.list_.addItem(self._add_listitem(user['Name'], user['Id'], user_image))
+
+        self.setFocus(self.list_)
+
+    @classmethod
+    def _add_listitem(cls, label, user_id, user_image):
+
+        item = xbmcgui.ListItem(label)
+        item.setProperty('id', user_id)
+        item.setArt({'Icon': user_image})
+
+        return item
+
+    def onAction(self, action):
+
+        if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR):
+            self.close()
+
+        if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK):
+
+            if self.getFocusId() == LIST:
+                user = self.list_.getSelectedItem()
+                selected_id = user.getProperty('id')
+                log.info('User Id selected: %s', selected_id)
+
+                for user in self.users:
+                    if user['Id'] == selected_id:
+                        self._user = user
+                        break
+
+                self.close()
+
+    def onClick(self, control):
+
+        if control == MANUAL:
+            self._manual_login = True
+            self.close()
+
+        elif control == CANCEL:
+            self.close()
+
+    def _get_user_artwork(self, user_id, art_type):
+        # Load user information set by UserClient
+        return "%s/emby/Users/%s/Images/%s?Format=original" % (self.server, user_id, art_type)
diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py
index a74b634f..bdb9ad48 100644
--- a/resources/lib/downloadutils.py
+++ b/resources/lib/downloadutils.py
@@ -10,7 +10,7 @@ import xbmc
 import xbmcgui
 
 import clientinfo
-from utils import window, settings
+from utils import window, settings, language as lang
 
 ##################################################################################################
 
@@ -40,11 +40,6 @@ class DownloadUtils():
         self.__dict__ = self._shared_state
 
 
-    def setUsername(self, username):
-        # Reserved for userclient only
-        self.username = username
-        log.debug("Set username: %s" % username)
-
     def setUserId(self, userId):
         # Reserved for userclient only
         self.userId = userId
@@ -60,12 +55,10 @@ class DownloadUtils():
         self.token = token
         log.debug("Set token: %s" % token)
 
-    def setSSL(self, ssl, sslclient):
+    def setSSL(self, ssl):
         # Reserved for userclient only
         self.sslverify = ssl
-        self.sslclient = sslclient
-        log.debug("Verify SSL host certificate: %s" % ssl)
-        log.debug("SSL client side certificate: %s" % sslclient)
+        log.debug("Verify SSL verify/certificate: %s" % ssl)
 
 
     def postCapabilities(self, deviceId):
@@ -149,8 +142,6 @@ class DownloadUtils():
         # If user enabled host certificate verification
         try:
             verify = self.sslverify
-            if self.sslclient is not None:
-                verify = self.sslclient
         except:
             log.info("Could not load SSL settings.")
 
@@ -214,47 +205,31 @@ class DownloadUtils():
         default_link = ""
 
         try:
-            if authenticate:
+            if self.s is not None:
+                session = self.s
+            else:
+                # request session does not exists
+                # Get user information
+                self.userId = window('emby_currUser')
+                self.server = window('emby_server%s' % self.userId)
+                self.token = window('emby_accessToken%s' % self.userId)
+                verifyssl = False
 
-                if self.s is not None:
-                    session = self.s
-                else:
-                    # request session does not exists
-                    # Get user information
-                    self.userId = window('emby_currUser')
-                    self.server = window('emby_server%s' % self.userId)
-                    self.token = window('emby_accessToken%s' % self.userId)
-                    verifyssl = False
-
-                    # IF user enables ssl verification
-                    if settings('sslverify') == "true":
-                        verifyssl = True
-                    if settings('sslcert') != "None":
-                        verifyssl = settings('sslcert')
-
-                    kwargs.update({
-                        'verify': verifyssl,
-                        'headers': self.getHeader()
-                    })
-
-                # Replace for the real values
-                url = url.replace("{server}", self.server)
-                url = url.replace("{UserId}", self.userId)
-
-            else: # User is not authenticated
-                # If user enables ssl verification
-                try:
-                    verifyssl = self.sslverify
-                    if self.sslclient is not None:
-                        verifyssl = self.sslclient
-                except AttributeError:
-                    verifyssl = False
+                # IF user enables ssl verification
+                if settings('sslverify') == "true":
+                    verifyssl = True
+                if settings('sslcert') != "None":
+                    verifyssl = settings('sslcert')
 
                 kwargs.update({
                     'verify': verifyssl,
-                    'headers': self.getHeader(authenticate=False)
+                    'headers': self.getHeader(authenticate)
                 })
 
+            # Replace for the real values
+            url = url.replace("{server}", self.server)
+            url = url.replace("{UserId}", self.userId)
+
             ##### PREPARE REQUEST #####
             kwargs.update({
                 'url': url,
@@ -310,13 +285,15 @@ class DownloadUtils():
                     # Emby server errors
                     if r.headers['X-Application-Error-Code'] == "ParentalControl":
                         # Parental control - access restricted
+                        if status != "restricted":
+                            xbmcgui.Dialog().notification(
+                                                    heading=lang(29999),
+                                                    message="Access restricted.",
+                                                    icon=xbmcgui.NOTIFICATION_ERROR,
+                                                    time=5000)
+                        
                         window('emby_serverStatus', value="restricted")
-                        xbmcgui.Dialog().notification(
-                                                heading="Emby server",
-                                                message="Access restricted.",
-                                                icon=xbmcgui.NOTIFICATION_ERROR,
-                                                time=5000)
-                        return False
+                        raise Warning('restricted')
 
                     elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
                         # User tried to do something his emby account doesn't allow
@@ -330,7 +307,7 @@ class DownloadUtils():
                                             heading="Error connecting",
                                             message="Unauthorized.",
                                             icon=xbmcgui.NOTIFICATION_ERROR)
-                    return 401
+                    raise Warning('401')
 
             elif r.status_code in (301, 302):
                 # Redirects
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index e532e5e2..6074280c 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -17,6 +17,7 @@ import xbmcplugin
 import artwork
 import utils
 import clientinfo
+import connectmanager
 import downloadutils
 import librarysync
 import read_embyserver as embyserver
@@ -102,7 +103,6 @@ def doMainListing():
         addDirectoryItem(lang(33052),
             "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root")
 
-    # some extra entries for settings and stuff. TODO --> localize the labels
     addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords")
     addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings")
     addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser")
@@ -115,6 +115,26 @@ def doMainListing():
     
     xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
+def emby_connect():
+    # Login user to emby connect
+    connect = connectmanager.ConnectManager()
+    try:
+        connectUser = connect.login_connect()
+    except RuntimeError:
+        return
+    else:
+        user = connectUser['User']
+        token = connectUser['AccessToken']
+        username = user['Name']
+        icon = user.get('ImageUrl') or "special://home/addons/plugin.video.emby/icon.png"
+        xbmcgui.Dialog().notification(heading=lang(29999),
+                                      message="%s %s" % (lang(33000), username.decode('utf-8')),
+                                      icon=icon,
+                                      time=1500,
+                                      sound=False)
+        
+        settings('connectUsername', value=username)
+
 ##### Generate a new deviceId
 def resetDeviceId():
 
diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index 032affb1..f59dc47b 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -2,174 +2,125 @@
 
 #################################################################################################
 
-import json
 import logging
-import socket
 
 import xbmc
 import xbmcgui
-import xbmcaddon
 
 import clientinfo
-import downloadutils
+import connectmanager
+import connect.connectionmanager as connectionmanager
 import userclient
 from utils import settings, language as lang, passwordsXML
 
 #################################################################################################
 
 log = logging.getLogger("EMBY."+__name__)
+STATE = connectionmanager.ConnectionState
 
 #################################################################################################
 
 
-class InitialSetup():
+class InitialSetup(object):
 
 
     def __init__(self):
 
-        self.addonId = clientinfo.ClientInfo().getAddonId()
-        self.doUtils = downloadutils.DownloadUtils().downloadUrl
-        self.userClient = userclient.UserClient()
+        self.addon_id = clientinfo.ClientInfo().getAddonId()
+        self.user_client = userclient.UserClient()
+        self.connectmanager = connectmanager.ConnectManager()
 
 
     def setup(self):
         # Check server, user, direct paths, music, direct stream if not direct path.
-        addonId = self.addonId
+        addon_id = self.addon_id
         dialog = xbmcgui.Dialog()
 
         ##### SERVER INFO #####
-        
+
         log.debug("Initial setup called.")
-        server = self.userClient.getServer()
 
-        if server:
-            log.debug("Server is already set.")
+        ###$ Begin transition phase $###
+        if settings('server') == "":
+            current_server = self.user_client.get_server()
+            self.connectmanager.get_server(current_server)
+            self.user_client.get_userid()
+            self.user_client.get_token()
+        ###$ End transition phase $###
+
+        current_state = self.connectmanager.get_state()
+        if current_state['State'] == STATE['SignedIn']:
+            server = current_state['Servers'][0]
+            server_address = self.connectmanager.get_address(server)
+            self._set_server(server_address, server['Name'])
+            self._set_user(server['UserId'], server['AccessToken'])
             return
-        
-        log.debug("Looking for server...")
-        server = self.getServerDetails()
-        log.debug("Found: %s" % server)
+
         try:
-            prefix, ip, port = server.replace("/", "").split(":")
-        except Exception: # Failed to retrieve server information
-            log.error("getServerDetails failed.")
-            xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
-            return
-        else:
-            server_confirm = dialog.yesno(
-                                heading=lang(29999),
-                                line1=lang(33034),
-                                line2="%s %s" % (lang(30169), server))
-            if server_confirm:
-                # Correct server found
-                log.info("Server is selected. Saving the information.")
-                settings('ipaddress', value=ip)
-                settings('port', value=port)
+            server = self.connectmanager.select_servers()
+            log.info("Server: %s", server)
 
-                if prefix == "https":
-                    settings('https', value="true")
+        except RuntimeError as error:
+            log.exception(error)
+            xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id)
+            return
+
+        else:
+            server_address = self.connectmanager.get_address(server)
+            self._set_server(server_address, server['Name'])
+
+            if not server.get('AccessToken') and not server.get('UserId'):
+                try:
+                    user = self.connectmanager.login(server)
+                    log.info("User authenticated: %s", user)
+                except RuntimeError as error:
+                    log.exception(error)
+                    xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id)
+                    return
+                settings('username', value=user['User']['Name'])
+                self._set_user(user['User']['Id'], user['AccessToken'])
             else:
-                # User selected no or cancelled the dialog
-                log.info("No server selected.")
-                xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
-                return
-
-        ##### USER INFO #####
-        
-        log.info("Getting user list.")
-
-        result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False)
-        if result == "":
-            log.info("Unable to connect to %s" % server)
-            return
-
-        log.debug("Response: %s" % result)
-        # Process the list of users
-        usernames = []
-        users_hasPassword = []
-
-        for user in result:
-            # Username
-            name = user['Name']
-            usernames.append(name)
-            # Password
-            if user['HasPassword']:
-                name = "%s (secure)" % name
-            users_hasPassword.append(name)
-
-        log.info("Presenting user list: %s" % users_hasPassword)
-        user_select = dialog.select(lang(30200), users_hasPassword)
-        if user_select > -1:
-            selected_user = usernames[user_select]
-            log.info("Selected user: %s" % selected_user)
-            settings('username', value=selected_user)
-        else:
-            log.info("No user selected.")
-            xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
-            return
+                user = self.connectmanager.get_state()
+                settings('connectUsername', value=user['ConnectUser']['Name'])
+                self._set_user(server['UserId'], server['AccessToken'])
 
         ##### ADDITIONAL PROMPTS #####
 
-        directPaths = dialog.yesno(
-                            heading=lang(30511),
-                            line1=lang(33035),
-                            nolabel=lang(33036),
-                            yeslabel=lang(33037))
-        if directPaths:
+        direct_paths = dialog.yesno(heading=lang(30511),
+                                    line1=lang(33035),
+                                    nolabel=lang(33036),
+                                    yeslabel=lang(33037))
+        if direct_paths:
             log.info("User opted to use direct paths.")
             settings('useDirectPaths', value="1")
 
             # ask for credentials
-            credentials = dialog.yesno(
-                                heading=lang(30517),
-                                line1= lang(33038))
+            credentials = dialog.yesno(heading=lang(30517), line1=lang(33038))
             if credentials:
                 log.info("Presenting network credentials dialog.")
                 passwordsXML()
-        
-        musicDisabled = dialog.yesno(
-                            heading=lang(29999),
-                            line1=lang(33039))
-        if musicDisabled:
+
+        music_disabled = dialog.yesno(heading=lang(29999), line1=lang(33039))
+        if music_disabled:
             log.info("User opted to disable Emby music library.")
             settings('enableMusic', value="false")
         else:
             # Only prompt if the user didn't select direct paths for videos
-            if not directPaths:
-                musicAccess = dialog.yesno(
-                                    heading=lang(29999),
-                                    line1=lang(33040))
-                if musicAccess:
+            if not direct_paths:
+                music_access = dialog.yesno(heading=lang(29999), line1=lang(33040))
+                if music_access:
                     log.info("User opted to direct stream music.")
                     settings('streamMusic', value="true")
-                
-    def getServerDetails(self):
 
-        log.info("Getting Server Details from Network")
-        
-        MULTI_GROUP = ("<broadcast>", 7359)
-        MESSAGE = "who is EmbyServer?"
-        
-        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        sock.settimeout(6.0)
+    @classmethod
+    def _set_server(cls, server, name):
 
-        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
-        
-        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
-        sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
-        sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
-        
-        log.debug("MultiGroup      : %s" % str(MULTI_GROUP))
-        log.debug("Sending UDP Data: %s" % MESSAGE)
-        sock.sendto(MESSAGE, MULTI_GROUP)
-    
-        try:
-            data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
-            log.info("Received Response: %s" % data)
-        except Exception:
-            log.error("No UDP Response")
-            return None
-        else:
-            # Get the address
-            data = json.loads(data)
-            return data['Address']
\ No newline at end of file
+        settings('serverName', value=name)
+        settings('server', value=server)
+        log.info("Saved server information: %s", server)
+
+    @classmethod
+    def _set_user(cls, user_id, token):
+
+        settings('userId', value=user_id)
+        settings('token', value=token)
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index 73dc4c59..18d7b9f3 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -1207,6 +1207,10 @@ class TVShows(Items):
         artwork = self.artwork
         API = api.API(item)
 
+        if item.get('LocationType') == "Virtual": # TODO: Filter via api instead
+            log.info("Skipping virtual episode: %s", item['Name'])
+            return
+
         # If the item already exist in the local Kodi DB we'll perform a full item update
         # If the item doesn't exist, we'll add it to the database
         update_item = True
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
index b90a671b..ad5c6b82 100644
--- a/resources/lib/librarysync.py
+++ b/resources/lib/librarysync.py
@@ -905,6 +905,9 @@ class LibrarySync(threading.Thread):
 
         try:
             self.run_internal()
+        except Warning as e:
+            if "restricted" in e:
+                pass
         except Exception as e:
             window('emby_dbScan', clear=True)
             log.exception(e)
diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py
index 091e1730..448a9b59 100644
--- a/resources/lib/read_embyserver.py
+++ b/resources/lib/read_embyserver.py
@@ -3,6 +3,7 @@
 #################################################################################################
 
 import logging
+import hashlib
 
 import xbmc
 
@@ -571,4 +572,20 @@ class Read_EmbyServer():
     def deleteItem(self, itemid):
 
         url = "{server}/emby/Items/%s?format=json" % itemid
-        self.doUtils(url, action_type="DELETE")
\ No newline at end of file
+        self.doUtils(url, action_type="DELETE")
+
+    def getUsers(self, server):
+
+        url = "%s/emby/Users/Public?format=json" % server
+        users = self.doUtils(url, authenticate=False)
+
+        return users or []
+
+    def loginUser(self, server, username, password=None):
+
+        password = password or ""
+        url = "%s/emby/Users/AuthenticateByName?format=json" % server
+        data = {'username': username, 'password': hashlib.sha1(password).hexdigest()}
+        user = self.doUtils(url, postBody=data, action_type="POST", authenticate=False)
+
+        return user
\ No newline at end of file
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
index 37282efb..b5f8534b 100644
--- a/resources/lib/userclient.py
+++ b/resources/lib/userclient.py
@@ -2,18 +2,16 @@
 
 ##################################################################################################
 
-import hashlib
 import logging
 import threading
 
 import xbmc
 import xbmcgui
-import xbmcaddon
-import xbmcvfs
 
 import artwork
-import clientinfo
+import connectmanager
 import downloadutils
+import read_embyserver as embyserver
 from utils import window, settings, language as lang
 
 ##################################################################################################
@@ -22,426 +20,276 @@ log = logging.getLogger("EMBY."+__name__)
 
 ##################################################################################################
 
-
 class UserClient(threading.Thread):
 
-    # Borg - multiple instances, shared state
-    _shared_state = {}
+    _shared_state = {} # Borg
 
-    stop_thread = False
-    auth = True
-    retry = 0
+    _stop_thread = False
+    _user = None
+    _server = None
 
-    currUser = None
-    currUserId = None
-    currServer = None
-    currToken = None
-    HasAccess = True
-    AdditionalUser = []
-
-    userSettings = None
+    _auth = True
+    _has_access = True
 
 
     def __init__(self):
 
         self.__dict__ = self._shared_state
-        self.addon = xbmcaddon.Addon()
 
-        self.doUtils = downloadutils.DownloadUtils()
+        self.doutils = downloadutils.DownloadUtils()
+        self.download = self.doutils.downloadUrl
+        self.emby = embyserver.Read_EmbyServer()
 
         threading.Thread.__init__(self)
 
+    @classmethod
+    def get_username(cls):
+        return settings('username') or settings('connectUsername') or None
 
-    def getAdditionalUsers(self):
+    def get_user(self, data=None):
 
-        additionalUsers = settings('additionalUsers')
+        if data is not None:
+            self._user = data
+            self._set_user_server()
 
-        if additionalUsers:
-            self.AdditionalUser = additionalUsers.split(',')
+        return self._user
 
-    def getUsername(self):
+    def get_server_details(self):
+        return self._server
 
-        username = settings('username')
+    @classmethod
+    def get_server(cls):
 
-        if not username:
-            log.debug("No username saved.")
-            return ""
-
-        return username
-
-    def getLogLevel(self):
-
-        try:
-            logLevel = int(settings('logLevel'))
-        except ValueError:
-            logLevel = 0
-
-        return logLevel
-
-    def getUserId(self):
-
-        username = self.getUsername()
-        w_userId = window('emby_currUser')
-        s_userId = settings('userId%s' % username)
-
-        # Verify the window property
-        if w_userId:
-            if not s_userId:
-                # Save access token if it's missing from settings
-                settings('userId%s' % username, value=w_userId)
-            log.debug("Returning userId from WINDOW for username: %s UserId: %s"
-                % (username, w_userId))
-            return w_userId
-        # Verify the settings
-        elif s_userId:
-            log.debug("Returning userId from SETTINGS for username: %s userId: %s"
-                % (username, s_userId))
-            return s_userId
-        # No userId found
-        else:
-            log.info("No userId saved for username: %s." % username)
-
-    def getServer(self, prefix=True):
-
-        alternate = settings('altip') == "true"
-        if alternate:
-            # Alternate host
-            HTTPS = settings('secondhttps') == "true"
-            host = settings('secondipaddress')
-            port = settings('secondport')
-        else:
-            # Original host
-            HTTPS = settings('https') == "true"
+        ###$ Begin transition phase $###
+        if settings('server') == "":
+            http = "https" if settings('https') == "true" else "http"
             host = settings('ipaddress')
             port = settings('port')
 
-        server = host + ":" + port
+            if host and port:
+                settings('server', value="%s://%s:%s" % (http, host, port))
+        ###$ End transition phase $###
 
-        if not host:
-            log.debug("No server information saved.")
-            return False
+        return settings('server') or None
 
-        # 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 verify_server(self):
 
-    def getToken(self):
-
-        username = self.getUsername()
-        userId = self.getUserId()
-        w_token = window('emby_accessToken%s' % userId)
-        s_token = settings('accessToken')
-
-        # Verify the window property
-        if w_token:
-            if not s_token:
-                # Save access token if it's missing from settings
-                settings('accessToken', value=w_token)
-                log.debug("Returning accessToken from WINDOW for username: %s accessToken: %s"
-                % (username, w_token))
-            return w_token
-        # Verify the settings
-        elif s_token:
-            log.debug("Returning accessToken from SETTINGS for username: %s accessToken: %s"
-                % (username, s_token))
-            window('emby_accessToken%s' % username, value=s_token)
-            return s_token
-        else:
-            log.info("No token found.")
-            return ""
-
-    def getSSLverify(self):
-        # Verify host certificate
-        s_sslverify = settings('sslverify')
-        if settings('altip') == "true":
-            s_sslverify = settings('secondsslverify')
-
-        if s_sslverify == "true":
+        url = "%s/emby/Users/Public?format=json" % self.get_server()
+        result = self.download(url, authenticate=False)
+        if result != "": # Specific verification, due to possibility of returning empty dict
             return True
-        else:
-            return False
-
-    def getSSL(self):
-        # Client side certificate
-        s_cert = settings('sslcert')
-        if settings('altip') == "true":
-            s_cert = settings('secondsslcert')
-
-        if s_cert == "None":
-            return None
-        else:
-            return s_cert
-
-    def setUserPref(self):
-
-        doUtils = self.doUtils.downloadUrl
-
-        result = doUtils("{server}/emby/Users/{UserId}?format=json")
-        self.userSettings = result
-        # Set user image for skin display
-        if result.get('PrimaryImageTag'):
-            window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
-
-        # Set resume point max
-        result = doUtils("{server}/emby/System/Configuration?format=json")
-        settings('markPlayed', value=str(result['MaxResumePct']))
-
-    def getPublicUsers(self):
-        # Get public Users
-        url = "%s/emby/Users/Public?format=json" % self.getServer()
-        result = self.doUtils.downloadUrl(url, authenticate=False)
-        if result != "":
-            return result
         else:
             # Server connection failed
             return False
 
+    @classmethod
+    def get_ssl(cls):
+        """
+            Returns boolean value or path to certificate
+            True: Verify ssl
+            False: Don't verify connection
+        """
+        certificate = settings('sslcert')
+        if certificate != "None":
+            return certificate
 
-    def hasAccess(self):
-        # hasAccess is verified in service.py
-        result = self.doUtils.downloadUrl("{server}/emby/Users?format=json")
+        return True if settings('sslverify') == "true" else False
 
-        if result == False:
-            # Access is restricted, set in downloadutils.py via exception
-            log.info("Access is restricted.")
-            self.HasAccess = False
+    def get_access(self):
 
-        elif window('emby_online') != "true":
-            # Server connection failed
-            pass
+        if not self._has_access:
+            self._set_access()
 
-        elif window('emby_serverStatus') == "restricted":
-            log.info("Access is granted.")
-            self.HasAccess = True
-            window('emby_serverStatus', clear=True)
-            xbmcgui.Dialog().notification(lang(29999), lang(33007))
+        return self._has_access
 
-    def loadCurrUser(self, authenticated=False):
+    def _set_access(self):
 
-        doUtils = self.doUtils
-        username = self.getUsername()
-        userId = self.getUserId()
+        try:
+            self.download("{server}/emby/Users?format=json")
+        except Warning as error:
+            if self._has_access and "restricted" in error:
+                self._has_access = False
+                log.info("Access is restricted")
+        else:
+            if not self._has_access:
+                self._has_access = True
+                window('emby_serverStatus', clear=True)
+                log.info("Access is granted")
+                xbmcgui.Dialog().notification(lang(29999), lang(33007))
 
-        # 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()
+    @classmethod
+    def get_userid(cls):
 
-        # Test the validity of current token
-        if authenticated == False:
-            url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
-            window('emby_currUser', value=userId)
-            window('emby_accessToken%s' % userId, value=self.currToken)
-            result = doUtils.downloadUrl(url)
+        ###$ Begin transition phase $###
+        if settings('userId') == "":
+            settings('userId', value=settings('userId%s' % settings('username')))
+        ###$ End transition phase $###
 
-            if result == 401:
-                # Token is no longer valid
-                self.resetClient()
-                return False
+        return settings('userId') or None
 
-        # Set to windows property
-        window('emby_currUser', value=userId)
-        window('emby_accessToken%s' % userId, value=self.currToken)
-        window('emby_server%s' % userId, value=self.currServer)
-        window('emby_server_%s' % userId, value=self.getServer(prefix=False))
+    @classmethod
+    def get_token(cls):
 
-        # 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()
+        ###$ Begin transition phase $###
+        if settings('token') == "":
+            settings('token', value=settings('accessToken'))
+        ###$ End transition phase $###
 
+        return settings('token') or None
 
-    def authenticate(self):
+    def _set_user_server(self):
 
-        dialog = xbmcgui.Dialog()
+        self._server = self.download("{server}/emby/System/Configuration?format=json")
+        settings('markPlayed', value=str(self._server['MaxResumePct']))
 
-        # Get /profile/addon_data
-        addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
-        hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
+        self._user = self.download("{server}/emby/Users/{UserId}?format=json")
+        if "PrimaryImageTag" in self._user:
+            window('EmbyUserImage',
+                   value=artwork.Artwork().getUserArtwork(self._user['Id'], 'Primary'))
 
-        username = self.getUsername()
-        server = self.getServer()
+    def _authenticate(self):
 
-        # If there's no settings.xml
-        if not hasSettings:
-            log.info("No settings.xml found.")
-            self.auth = False
-            return
-        # If no user information
-        elif not server or not username:
-            log.info("Missing server information.")
-            self.auth = False
-            return
-        # If there's a token, load the user
-        elif self.getToken():
-            result = self.loadCurrUser()
+        if not self.get_server() or not self.get_username():
+            log.info('missing server or user information')
+            self._auth = False
 
-            if result == False:
-                pass
+        elif self.get_token():
+            try:
+                self._load_user()
+            except Warning:
+                log.info("token is invalid")
             else:
-                log.info("Current user: %s" % self.currUser)
-                log.info("Current userId: %s" % self.currUserId)
-                log.debug("Current accessToken: %s" % self.currToken)
+                log.info("current user: %s", self.get_username())
+                log.info("current userid: %s", self.get_userid())
+                log.debug("current token: %s", self.get_token())
                 return
 
         ##### AUTHENTICATE USER #####
+        server = self.get_server()
+        username = self.get_username().decode('utf-8')
+        users = self.emby.getUsers(server)
+        user_found = None
 
-        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 = dialog.input(
-                        heading="%s %s" % (lang(33008), username.decode('utf-8')),
-                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
-                    # If password dialog is cancelled
-                    if not password:
-                        log.warn("No password entered.")
-                        window('emby_serverStatus', value="Stop")
-                        self.auth = False
-                        return
+            if username == user['Name']:
+                user_found = user
                 break
-        else:
-            # Manual login, user is hidden
-            password = dialog.input(
-                            heading="%s %s" % (lang(33008), username.decode('utf-8')),
-                            option=xbmcgui.ALPHANUM_HIDE_INPUT)
-        sha1 = hashlib.sha1(password)
-        sha1 = sha1.hexdigest()
-
-        # Authenticate username and password
-        data = {'username': username, 'password': sha1}
-        log.debug(data)
-
-        url = "%s/emby/Users/AuthenticateByName?format=json" % server
-        result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False)
-
         try:
-            log.info("Auth response: %s" % result)
-            accessToken = result['AccessToken']
-
-        except (KeyError, TypeError):
-            log.info("Failed to retrieve the api key.")
-            accessToken = None
-
-        if accessToken is not None:
-            self.currUser = username
-            dialog.notification(lang(29999),
-                                "%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
-            settings('accessToken', value=accessToken)
-            settings('userId%s' % username, value=result['User']['Id'])
-            log.info("User Authenticated: %s" % accessToken)
-            self.loadCurrUser(authenticated=True)
-            window('emby_serverStatus', clear=True)
-            self.retry = 0
+            user = self.connectmanager.login_manual(server, user_found)
+        except RuntimeError:
+            window('emby_serverStatus', value="stop")
+            self._auth = False
+            return
         else:
-            log.error("User authentication failed.")
-            settings('accessToken', value="")
-            settings('userId%s' % username, value="")
-            dialog.ok(lang(33001), lang(33009))
+            log.info("user: %s", user)
+            settings('username', value=user['User']['Name'])
+            settings('token', value=user['AccessToken'])
+            settings('userId', value=user['User']['Id'])
+            xbmcgui.Dialog().notification(lang(29999),
+                                          "%s %s!" % (lang(33000), username))
+            self._load_user(authenticated=True)
+            window('emby_serverStatus', clear=True)
 
-            # Give two attempts at entering password
-            if self.retry == 2:
-                log.info("Too many retries. "
-                    "You can retry by resetting attempts in the addon settings.")
-                window('emby_serverStatus', value="Stop")
-                dialog.ok(lang(33001), lang(33010))
+    def _load_user(self, authenticated=False):
 
-            self.retry += 1
-            self.auth = False
+        doutils = self.doutils
 
-    def resetClient(self):
+        userid = self.get_userid()
+        server = self.get_server()
+        token = self.get_token()
 
-        log.info("Reset UserClient authentication.")
-        if self.currToken is not None:
-            # In case of 401, removed saved token
-            settings('accessToken', value="")
-            window('emby_accessToken%s' % self.getUserId(), clear=True)
-            self.currToken = None
-            log.info("User token has been removed.")
+        # Set properties
+        window('emby_currUser', value=userid)
+        window('emby_server%s' % userid, value=server)
+        window('emby_accessToken%s' % userid, value=token)
 
-        self.auth = True
-        self.currUser = None
+        # Test the validity of the current token
+        if not authenticated:
+            try:
+                self.download("{server}/emby/Users/{UserId}?format=json")
+            except Warning as error:
+                if "401" in error:
+                    # Token is not longer valid
+                    raise
+
+        # Set downloadutils.py values
+        doutils.setUserId(userid)
+        doutils.setServer(server)
+        doutils.setToken(token)
+        doutils.setSSL(self.get_ssl())
+
+        # Start downloadutils.py session
+        doutils.startSession()
+
+        # Set _user and _server
+        # verify user access
+        try:
+            self._set_access()
+            self._set_user_server()
+        except Warning: # We don't need to raise any exceptions
+            pass
+
+    def _reset_client(self):
+
+        log.info("reset UserClient authentication")
+
+        settings('accessToken', value="")
+        window('emby_accessToken', clear=True)
+
+        log.info("user token revoked.")
+
+        self._user = None
+        self.auth = None
 
     def run(self):
 
         monitor = xbmc.Monitor()
+        self.connectmanager = connectmanager.ConnectManager()
+
         log.warn("----===## Starting UserClient ##===----")
 
-        while not monitor.abortRequested():
+        while not self._stop_thread:
 
             status = window('emby_serverStatus')
             if status:
                 # Verify the connection status to server
                 if status == "restricted":
                     # Parental control is restricting access
-                    self.HasAccess = False
+                    self._has_access = False
 
                 elif status == "401":
                     # Unauthorized access, revoke token
-                    window('emby_serverStatus', value="Auth")
-                    self.resetClient()
+                    window('emby_serverStatus', value="auth")
+                    self._reset_client()
 
-            if self.auth and (self.currUser is None):
+            if self._auth and self._user is None:
                 # Try to authenticate user
                 status = window('emby_serverStatus')
-                if not status or status == "Auth":
+                if not status or status == "auth":
                     # Set auth flag because we no longer need
                     # to authenticate the user
-                    self.auth = False
-                    self.authenticate()
+                    self._auth = False
+                    self._authenticate()
 
-
-            if not self.auth and (self.currUser is None):
+            if not self._auth and self._user is None:
                 # If authenticate failed.
-                server = self.getServer()
-                username = self.getUsername()
+                server = self.get_server()
+                username = self.get_username()
                 status = window('emby_serverStatus')
 
                 # The status Stop is for when user cancelled password dialog.
-                if server and username and status != "Stop":
+                if server and username and status != "stop":
                     # Only if there's information found to login
-                    log.debug("Server found: %s" % server)
-                    log.debug("Username found: %s" % username)
-                    self.auth = True
-
-
-            if self.stop_thread == True:
-                # If stopping the client didn't work
-                break
+                    log.info("Server found: %s", server)
+                    log.info("Username found: %s", username)
+                    self._auth = True
 
             if monitor.waitForAbort(1):
                 # Abort was requested while waiting. We should exit
                 break
 
-        self.doUtils.stopSession()
+        self.doutils.stopSession()
         log.warn("##===---- UserClient Stopped ----===##")
 
-    def stopClient(self):
-        # When emby for kodi terminates
-        self.stop_thread = True
\ No newline at end of file
+    def stop_client(self):
+        self._stop_thread = True
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 9d38e710..b1dfac81 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -408,12 +408,14 @@ def reset():
     # Remove emby info
     resp = dialog.yesno(language(29999), language(33087))
     if resp:
+        import connectmanager
         # Delete the settings
         addon = xbmcaddon.Addon()
         addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
         dataPath = "%ssettings.xml" % addondir
         xbmcvfs.delete(dataPath)
         log.info("Deleting: settings.xml")
+        connectmanager.ConnectManager().clear_data()
 
     dialog.ok(heading=language(29999), line1=language(33088))
     xbmc.executebuiltin('RestartApp')
diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py
index bc158338..c79e81d0 100644
--- a/resources/lib/websocket_client.py
+++ b/resources/lib/websocket_client.py
@@ -251,7 +251,7 @@ class WebSocket_Client(threading.Thread):
 
         elif messageType == "UserConfigurationUpdated":
             # Update user data set in userclient
-            userclient.UserClient().userSettings = data
+            userclient.UserClient().get_user(data)
             self.librarySync.refresh_views = True
 
     def on_close(self, ws):
diff --git a/resources/settings.xml b/resources/settings.xml
index ac6ddd0f..b29f8e8d 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -1,27 +1,24 @@
 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
 <settings>
 	<category label="30014"><!-- Emby -->
-		<!-- Primary address -->
-		<setting id="ipaddress" label="30000" type="text" default="" />
-		<setting id="port" label="30030" type="number" default="8096" />
-		<setting id="https" label="30243" type="bool" default="false" />
-		<setting id="sslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
-		<setting id="sslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
-		<!-- Secondary address -->
-		<setting id="altip" label="30502" type="bool" default="false" />
-		<setting id="secondipaddress" subsetting="true" label="30503" type="text" default="" visible="eq(-1,true)" />
-		<setting id="secondport" subsetting="true" label="30030" type="number" default="8096" visible="eq(-2,true)" />
-		<setting id="secondhttps" subsetting="true" label="30243" type="bool" default="false" visible="eq(-3,true)" />
-		<setting id="secondsslverify" subsetting="true" label="30500" type="bool" default="false" visible="eq(-1,true)" />
-		<setting id="secondsslcert" subsetting="true" label="30501" type="file" default="None" visible="eq(-2,true)" />
+		<setting id="idMethod" label="Login method" type="enum" values="Manual|Emby Connect" default="0" />
+		<!-- Manual address -->
+		<setting id="username" label="30024" type="text" default="" visible="eq(-1,0)" />
+		<setting id="serverName" label="30000" type="text" default="" />
+		<setting id="server" type="text" default="" visible="false" />
+		<setting id="sslverify" label="30500" type="bool" default="false" visible="eq(-1,true)" subsetting="true" />
+		<setting id="sslcert" label="30501" type="file" default="None" visible="eq(-2,true)" subsetting="true" />
+		<!-- Emby Connect -->
+		<setting id="connectUsername" label="30543" type="text" default="" visible="!eq(0,) + eq(-7,1)" />
+		<setting label="30600" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=connect)" visible="eq(-7,1) + eq(-1,)" option="close" />
+		<setting label="30618" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=connect)" visible="eq(-8,1) + !eq(-2,)" option="close" />
 		<!-- User settings -->
-		<setting id="username" label="30024" type="text" default="" />
+		<setting id="accessToken" type="text" default="" visible="false" />
+		<setting id="userId" type="text" default="" visible="false" />
+		<!-- Device settings -->
 		<setting type="sep" />
 		<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
-		<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
-		<setting label="30505" type="action" visible="eq(1,) + !eq(-15,)" action="RunPlugin(plugin://plugin.video.emby?mode=resetauth)" option="close" />
-		<setting id="accessToken" type="text" visible="false" default="" />
-		<setting id="pathsub" type="bool" visible="false" default="false" />
+		<setting id="deviceName" label="30016" type="text" default="Kodi" visible="eq(-1,true)" />
 	</category>
 
 	<category label="30506"><!-- Sync Options -->
diff --git a/resources/skins/default/1080i/script-emby-connect-login-manual.xml b/resources/skins/default/1080i/script-emby-connect-login-manual.xml
new file mode 100644
index 00000000..57e4a88a
--- /dev/null
+++ b/resources/skins/default/1080i/script-emby-connect-login-manual.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+	<defaultcontrol always="true">200</defaultcontrol>
+	<zorder>0</zorder>
+	<include>dialogeffect</include>
+	<controls>
+		<control type="image">
+			<description>Background fade</description>
+			<width>100%</width>
+			<height>100%</height>
+			<texture>emby-bg-fade.png</texture>
+		</control>
+		
+		<control type="group">
+			<width>600</width>
+			<left>35%</left>
+			<top>20%</top>
+			<control type="image">
+				<description>Background box</description>
+				<texture colordiffuse="ff111111">white.png</texture>
+				<width>600</width>
+				<height>480</height>
+			</control>
+
+			<control type="group" id="202">
+				<top>485</top>
+				<visible>False</visible>
+				<control type="image">
+					<description>Error box</description>
+					<texture colordiffuse="ff222222">white.png</texture>
+					<width>100%</width>
+					<height>50</height>
+				</control>
+
+				<control type="label" id="203">
+					<description>Error message</description>
+					<textcolor>white</textcolor>
+					<font>font10</font>
+					<aligny>center</aligny>
+					<align>center</align>
+					<height>50</height>
+				</control>
+			</control>
+			
+			<control type="image">
+				<description>Emby logo</description>
+				<texture>logo-white.png</texture>
+				<aspectratio>keep</aspectratio>
+				<width>120</width>
+				<height>49</height>
+				<top>30</top>
+				<left>25</left>
+			</control>
+			
+			<control type="group">
+				<width>500</width>
+				<left>50</left>
+				<control type="label">
+					<description>Please sign in</description>
+					<label>$ADDON[plugin.video.emby 30612]</label>
+					<textcolor>white</textcolor>
+					<font>font12</font>
+					<aligny>top</aligny>
+					<align>center</align>
+					<width>100%</width>
+					<top>100</top>
+				</control>
+				
+				<control type="group">
+					<top>150</top>
+					<control type="label">
+						<description>Username</description>
+						<label>$ADDON[plugin.video.emby 30024]</label>
+						<textcolor>ffa6a6a6</textcolor>
+						<font>font10</font>
+						<aligny>top</aligny>
+					</control>
+
+					<control type="image">
+						<description>separator</description>
+						<width>102%</width>
+						<height>0.5</height>
+						<top>66</top>
+						<left>-10</left>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
+					</control>
+				</control>
+				
+				<control type="group">
+					<description>Password</description>
+					<top>225</top>
+					<control type="label">
+						<description>Password label</description>
+						<label>$ADDON[plugin.video.emby 30602]</label>
+						<textcolor>ffa6a6a6</textcolor>
+						<font>font10</font>
+						<aligny>top</aligny>
+					</control>
+
+					<control type="image">
+						<description>separator</description>
+						<width>102%</width>
+						<height>0.5</height>
+						<top>66</top>
+						<left>-10</left>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
+					</control>
+				</control>
+
+				<control type="group">
+					<description>Buttons</description>
+					<top>335</top>
+					<control type="button" id="200">
+						<description>Sign in</description>
+						<texturenofocus border="5" colordiffuse="ff0b8628">box.png</texturenofocus>
+						<texturefocus border="5" colordiffuse="ff13a134">box.png</texturefocus>
+						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30605][/B][/UPPERCASE]</label>
+						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
+						<align>center</align>
+						<width>100%</width>
+						<height>50</height>
+						<ondown>201</ondown>
+					</control>
+					
+					<control type="button" id="201">
+						<description>Cancel</description>
+						<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+						<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
+						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
+						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
+						<align>center</align>
+						<width>100%</width>
+						<height>50</height>
+						<top>55</top>
+						<onup>200</onup>
+					</control>
+				</control>
+			</control>
+		</control>
+	</controls>
+</window>
\ No newline at end of file
diff --git a/resources/skins/default/1080i/script-emby-connect-login.xml b/resources/skins/default/1080i/script-emby-connect-login.xml
index b5b01632..cb69387c 100644
--- a/resources/skins/default/1080i/script-emby-connect-login.xml
+++ b/resources/skins/default/1080i/script-emby-connect-login.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <window>
-	<defaultcontrol always="false">200</defaultcontrol>
+	<defaultcontrol always="true">200</defaultcontrol>
 	<zorder>0</zorder>
 	<include>dialogeffect</include>
 	<controls>
@@ -13,21 +13,41 @@
 		
 		<control type="group">
 			<width>600</width>
-			<left>33%</left>
+			<left>35%</left>
 			<top>15%</top>
 			<control type="image">
 				<description>Background box</description>
-				<texture border="6" colordiffuse="ff111111">box.png</texture>
+				<texture colordiffuse="ff111111">white.png</texture>
 				<width>600</width>
 				<height>700</height>
 			</control>
+
+			<control type="group" id="202">
+				<top>705</top>
+				<visible>False</visible>
+				<control type="image">
+					<description>Error box</description>
+					<texture colordiffuse="ff222222">white.png</texture>
+					<width>100%</width>
+					<height>50</height>
+				</control>
+
+				<control type="label" id="203">
+					<description>Error message</description>
+					<textcolor>white</textcolor>
+					<font>font10</font>
+					<aligny>center</aligny>
+					<align>center</align>
+					<height>50</height>
+				</control>
+			</control>
 			
 			<control type="image">
 				<description>Emby logo</description>
 				<texture>logo-white.png</texture>
 				<width>160</width>
 				<height>49</height>
-				<top>20</top>
+				<top>30</top>
 				<left>25</left>
 			</control>
 			
@@ -47,7 +67,7 @@
 					<top>190</top>
 					<control type="label">
 						<description>Username email</description>
-						<label>$ADDON[plugin.video.emby 30601]</label>
+						<label>$ADDON[plugin.video.emby 30543]</label>
 						<textcolor>ffa6a6a6</textcolor>
 						<font>font10</font>
 						<aligny>top</aligny>
@@ -59,7 +79,7 @@
 						<height>0.5</height>
 						<top>66</top>
 						<left>-10</left>
-						<texture colordiffuse="ff222222" border="90,3,90,3">separator.png</texture>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
 					</control>
 				</control>
 				
@@ -80,7 +100,7 @@
 						<height>0.5</height>
 						<top>66</top>
 						<left>-10</left>
-						<texture colordiffuse="ff222222" border="90,3,90,3">separator.png</texture>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
 					</control>
 				</control>
 
@@ -89,10 +109,12 @@
 					<top>385</top>
 					<control type="button" id="200">
 						<description>Sign in</description>
-						<texturenofocus border="5" colordiffuse="green">box.png</texturenofocus>
-						<texturefocus border="5" colordiffuse="green">box.png</texturefocus>
+						<texturenofocus border="5" colordiffuse="ff0b8628">box.png</texturenofocus>
+						<texturefocus border="5" colordiffuse="ff13a134">box.png</texturefocus>
 						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30605][/B][/UPPERCASE]</label>
 						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
 						<align>center</align>
 						<width>100%</width>
 						<height>50</height>
@@ -100,11 +122,13 @@
 					</control>
 					
 					<control type="button" id="201">
-						<description>Later</description>
+						<description>Cancel</description>
 						<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
-						<texturefocus border="5" colordiffuse="ff464646">box.png</texturefocus>
+						<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
 						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
 						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
 						<align>center</align>
 						<width>100%</width>
 						<height>50</height>
@@ -124,30 +148,32 @@
 						<wrapmultiline>true</wrapmultiline>
 						<aligny>top</aligny>
 						<width>340</width>
+						<height>100%</height>
 					</control>
 
-					<control type="image">
-						<description>qrcode</description>
-						<texture>qrcode_disclaimer.png</texture>
-						<width>140</width>
-						<height>140</height>
-						<top>10</top>
-						<left>360</left>
-					</control>
+					<control type="group">
+						<control type="label">
+							<description>Scan me</description>
+							<label>[UPPERCASE]$ADDON[plugin.video.emby 30604][/UPPERCASE]</label>
+							<font>font12</font>
+							<textcolor>ff0b8628</textcolor>
+							<aligny>top</aligny>
+							<width>200</width>
+							<top>120</top>
+							<left>230</left>
+						</control>
 
-					<control type="label">
-						<description>Scan me</description>
-						<label>[UPPERCASE]$ADDON[plugin.video.emby 30604][/UPPERCASE]</label>
-						<font>font12</font>
-						<textcolor>green</textcolor>
-						<align>right</align>
-						<aligny>top</aligny>
-						<top>120</top>
-						<right>160</right>
+						<control type="image">
+							<description>qrcode</description>
+							<texture>qrcode_disclaimer.png</texture>
+							<width>140</width>
+							<height>140</height>
+							<top>10</top>
+							<left>360</left>
+						</control>
 					</control>
 				</control>
 			</control>
 		</control>
-
 	</controls>
 </window>
\ No newline at end of file
diff --git a/resources/skins/default/1080i/script-emby-connect-server-manual.xml b/resources/skins/default/1080i/script-emby-connect-server-manual.xml
new file mode 100644
index 00000000..27e50037
--- /dev/null
+++ b/resources/skins/default/1080i/script-emby-connect-server-manual.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+	<defaultcontrol always="true">200</defaultcontrol>
+	<zorder>0</zorder>
+	<include>dialogeffect</include>
+	<controls>
+		<control type="image">
+			<description>Background fade</description>
+			<width>100%</width>
+			<height>100%</height>
+			<texture>emby-bg-fade.png</texture>
+		</control>
+		
+		<control type="group">
+			<width>600</width>
+			<left>35%</left>
+			<top>20%</top>
+			<control type="image">
+				<description>Background box</description>
+				<texture colordiffuse="ff111111">white.png</texture>
+				<width>600</width>
+				<height>525</height>
+			</control>
+
+			<control type="group" id="202">
+				<top>530</top>
+				<visible>False</visible>
+				<control type="image">
+					<description>Error box</description>
+					<texture colordiffuse="ff222222">white.png</texture>
+					<width>100%</width>
+					<height>50</height>
+				</control>
+
+				<control type="label" id="203">
+					<description>Error message</description>
+					<textcolor>white</textcolor>
+					<font>font10</font>
+					<aligny>center</aligny>
+					<align>center</align>
+					<height>50</height>
+				</control>
+			</control>
+			
+			<control type="image">
+				<description>Emby logo</description>
+				<texture>logo-white.png</texture>
+				<aspectratio>keep</aspectratio>
+				<width>120</width>
+				<height>49</height>
+				<top>30</top>
+				<left>25</left>
+			</control>
+			
+			<control type="group">
+				<width>500</width>
+				<left>50</left>
+				<control type="label">
+					<description>Connect to server</description>
+					<label>$ADDON[plugin.video.emby 30614]</label>
+					<textcolor>white</textcolor>
+					<font>font12</font>
+					<aligny>top</aligny>
+					<align>center</align>
+					<width>100%</width>
+					<top>100</top>
+				</control>
+				
+				<control type="group">
+					<top>150</top>
+					<control type="label">
+						<description>Host</description>
+						<label>$ADDON[plugin.video.emby 30615]</label>
+						<textcolor>ffa6a6a6</textcolor>
+						<font>font10</font>
+						<aligny>top</aligny>
+					</control>
+
+					<control type="image">
+						<description>separator</description>
+						<width>102%</width>
+						<height>0.5</height>
+						<top>66</top>
+						<left>-10</left>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
+					</control>
+
+					<control type="label">
+						<description>Host example</description>
+						<label>192.168.1.100 or https://myserver.com</label>
+						<textcolor>ff464646</textcolor>
+						<font>font10</font>
+						<aligny>top</aligny>
+						<top>70</top>
+					</control>
+				</control>
+				
+				<control type="group">
+					<description>Port</description>
+					<top>275</top>
+					<control type="label">
+						<description>Port label</description>
+						<label>$ADDON[plugin.video.emby 30030]</label>
+						<textcolor>ffa6a6a6</textcolor>
+						<font>font10</font>
+						<aligny>top</aligny>
+					</control>
+
+					<control type="image">
+						<description>separator</description>
+						<width>102%</width>
+						<height>0.5</height>
+						<top>66</top>
+						<left>-10</left>
+						<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
+					</control>
+				</control>
+
+				<control type="group">
+					<description>Buttons</description>
+					<top>380</top>
+					<control type="button" id="200">
+						<description>Connect</description>
+						<texturenofocus border="5" colordiffuse="ff0b8628">box.png</texturenofocus>
+						<texturefocus border="5" colordiffuse="ff13a134">box.png</texturefocus>
+						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30616][/B][/UPPERCASE]</label>
+						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
+						<align>center</align>
+						<width>100%</width>
+						<height>50</height>
+						<ondown>201</ondown>
+					</control>
+					
+					<control type="button" id="201">
+						<description>Cancel</description>
+						<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+						<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
+						<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
+						<font>font10</font>
+						<textcolor>ffa6a6a6</textcolor>
+						<focusedcolor>white</focusedcolor>
+						<align>center</align>
+						<width>100%</width>
+						<height>50</height>
+						<top>55</top>
+						<onup>200</onup>
+					</control>
+				</control>
+			</control>
+		</control>
+	</controls>
+</window>
\ No newline at end of file
diff --git a/resources/skins/default/1080i/script-emby-connect-server.xml b/resources/skins/default/1080i/script-emby-connect-server.xml
new file mode 100644
index 00000000..01fc9141
--- /dev/null
+++ b/resources/skins/default/1080i/script-emby-connect-server.xml
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+	<defaultcontrol always="true">155</defaultcontrol>
+	<zorder>0</zorder>
+	<include>dialogeffect</include>
+	<controls>
+		<control type="image">
+			<description>Background fade</description>
+			<width>100%</width>
+			<height>100%</height>
+			<texture>emby-bg-fade.png</texture>
+		</control>
+		
+		<control type="group">
+			<width>450</width>
+			<left>38%</left>
+			<top>15%</top>
+			<control type="image">
+				<description>Background box</description>
+				<texture colordiffuse="ff111111">white.png</texture>
+				<width>450</width>
+				<height>710</height>
+			</control>
+
+			<control type="image">
+				<description>Emby logo</description>
+				<texture>logo-white.png</texture>
+				<aspectratio>keep</aspectratio>
+				<width>120</width>
+				<height>49</height>
+				<top>30</top>
+				<left>25</left>
+			</control>
+
+			<control type="group">
+				<description>User info</description>
+				<top>70</top>
+				<width>350</width>
+				<left>50</left>
+				<control type="image" id="150">
+					<description>User image</description>
+					<texture diffuse="user_image.png">userflyoutdefault.png</texture> 
+					<aspectratio>keep</aspectratio>
+					<align>center</align>
+					<width>100%</width>
+					<height>70</height>
+					<top>40</top>
+				</control>
+
+				<control type="image" id="204">
+					<description>Busy animation</description>
+					<align>center</align>
+					<top>23</top>
+					<width>100%</width>
+					<height>105</height>
+					<visible>False</visible>
+					<texture colordiffuse="ff13a134">fading_circle.png</texture>
+					<aspectratio>keep</aspectratio>
+					<animation effect="rotate" start="360" end="0" center="auto" time="2000" loop="true" condition="true">conditional</animation>
+				</control>
+
+				<control type="label" id="151">
+					<description>Welcome user</description>
+					<textcolor>white</textcolor>
+					<font>font12</font>
+					<align>center</align>
+					<aligny>top</aligny>
+					<top>120</top>
+					<width>100%</width>
+					<height>50</height>
+				</control>
+
+				<control type="image">
+					<description>separator</description>
+					<width>102%</width>
+					<height>0.5</height>
+					<top>165</top>
+					<left>-10</left>
+					<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
+				</control>
+
+				<control type="label">
+					<description>Select server</description>
+					<textcolor>ffa6a6a6</textcolor>
+					<label>$ADDON[plugin.video.emby 30607]</label>
+					<font>font10</font>
+					<align>center</align>
+					<aligny>top</aligny>
+					<top>170</top>
+					<width>100%</width>
+					<height>50</height>
+				</control>
+			</control>
+
+			<control type="group">
+				<top>290</top>
+				<width>100%</width>
+				<height>184</height>
+				<control type="list" id="155">
+					<description>Connect servers</description>
+					<focusposition>0</focusposition>
+					<width>100%</width>
+					<height>100%</height>
+					<top>10</top>
+					<left>55</left>
+					<onup>155</onup>
+					<ondown condition="Control.IsVisible(205)">205</ondown>
+					<ondown condition="!Control.IsVisible(205)">206</ondown>
+					<onleft condition="Control.IsVisible(205)">205</onleft>
+					<onleft condition="!Control.IsVisible(205)">206</onleft>
+					<onright>155</onright>
+					<pagecontrol>60</pagecontrol>
+					<scrolltime tween="sine" easing="out">250</scrolltime>
+					<itemlayout height="46">
+						<control type="group">
+							<width>45</width>
+							<height>45</height>
+							<control type="image">
+								<description>Network</description>
+								<aspectratio>keep</aspectratio>
+								<texture>network.png</texture>
+								<visible>StringCompare(ListItem.Property(server_type),network)</visible>
+							</control>
+							<control type="image">
+								<description>Wifi</description>
+								<aspectratio>keep</aspectratio>
+								<texture>wifi.png</texture>
+								<visible>StringCompare(ListItem.Property(server_type),wifi)</visible>
+							</control>
+						</control>
+
+						<control type="label">
+							<width>300</width>
+							<height>40</height>
+							<left>55</left>
+							<font>font10</font>
+							<aligny>center</aligny>
+							<textcolor>ff838383</textcolor>
+							<info>ListItem.Label</info>
+						</control>
+					</itemlayout>
+					<focusedlayout height="46">
+						<control type="group">
+							<width>45</width>
+							<height>45</height>
+							<control type="image">
+								<description>Network</description>
+								<aspectratio>keep</aspectratio>
+								<texture>network.png</texture>
+								<visible>StringCompare(ListItem.Property(server_type),network)</visible>
+							</control>
+							<control type="image">
+								<description>Wifi</description>
+								<aspectratio>keep</aspectratio>
+								<texture>wifi.png</texture>
+								<visible>StringCompare(ListItem.Property(server_type),wifi)</visible>
+							</control>
+						</control>
+
+						<control type="label">
+							<width>300</width>
+							<height>40</height>
+							<left>55</left>
+							<font>font10</font>
+							<aligny>center</aligny>
+							<textcolor>white</textcolor>
+							<info>ListItem.Label</info>
+							<visible>Control.HasFocus(155)</visible>
+						</control>
+						<control type="label">
+							<width>300</width>
+							<height>40</height>
+							<left>55</left>
+							<font>font10</font>
+							<aligny>center</aligny>
+							<textcolor>ff838383</textcolor>
+							<info>ListItem.Label</info>
+							<visible>!Control.HasFocus(155)</visible>
+						</control>
+					</focusedlayout>
+				</control>
+
+				<control type="scrollbar" id="60">
+					<left>395</left>
+					<top>10</top>
+					<width>5</width>
+					<height>100%</height>
+					<onleft>155</onleft>
+					<onup>60</onup>
+					<ondown>60</ondown>
+					<texturesliderbackground colordiffuse="ff000000" border="4">box.png</texturesliderbackground>
+					<texturesliderbar colordiffuse="ff222222" border="4">box.png</texturesliderbar>
+					<texturesliderbarfocus colordiffuse="ff222222" border="4">box.png</texturesliderbarfocus>
+					<showonepage>false</showonepage>
+				</control>
+
+				<control type="group">
+					<top>100%</top>
+					<height>220</height>
+					<control type="group">
+						<top>45</top>
+						<height>150</height>
+						<control type="button" id="205">
+							<visible>True</visible>
+							<description>Sign in Connect</description>
+							<texturenofocus border="5" colordiffuse="ff0b8628">box.png</texturenofocus>
+							<texturefocus border="5" colordiffuse="ff13a134">box.png</texturefocus>
+							<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30600][/B][/UPPERCASE]</label>
+							<font>font10</font>
+							<textcolor>ffa6a6a6</textcolor>
+							<focusedcolor>white</focusedcolor>
+							<align>center</align>
+							<width>350</width>
+							<height>50</height>
+							<left>50</left>
+							<onup>155</onup>
+							<ondown>206</ondown>
+						</control>
+
+						<control type="button" id="206">
+							<description>Manually add server</description>
+							<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+							<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
+							<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30611][/B][/UPPERCASE]</label>
+							<font>font10</font>
+							<textcolor>ffa6a6a6</textcolor>
+							<focusedcolor>white</focusedcolor>
+							<align>center</align>
+							<top>55</top>
+							<width>350</width>
+							<height>50</height>
+							<left>50</left>
+							<onup condition="Control.IsVisible(205)">205</onup>
+							<onup condition="!Control.IsVisible(205)">155</onup>
+							<ondown>201</ondown>
+						</control>
+
+						<control type="button" id="201">
+							<description>Cancel</description>
+							<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+							<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
+							<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
+							<font>font10</font>
+							<textcolor>ffa6a6a6</textcolor>
+							<focusedcolor>white</focusedcolor>
+							<align>center</align>
+							<top>110</top>
+							<width>350</width>
+							<height>50</height>
+							<left>50</left>
+							<onup>206</onup>
+						</control>
+					</control>
+
+					<control type="group" id="202">
+						<top>100%</top>
+						<visible>False</visible>
+						<control type="image">
+							<description>Message box</description>
+							<texture colordiffuse="ff222222">white.png</texture>
+							<width>100%</width>
+							<height>50</height>
+							<top>20</top>
+						</control>
+
+						<control type="label" id="203">
+							<description>Message</description>
+							<textcolor>white</textcolor>
+							<font>font10</font>
+							<aligny>center</aligny>
+							<align>center</align>
+							<height>50</height>
+							<top>20</top>
+						</control>
+					</control>
+				</control>
+			</control>
+		</control>
+	</controls>
+</window>
\ No newline at end of file
diff --git a/resources/skins/default/1080i/script-emby-connect-users.xml b/resources/skins/default/1080i/script-emby-connect-users.xml
new file mode 100644
index 00000000..0acd2b32
--- /dev/null
+++ b/resources/skins/default/1080i/script-emby-connect-users.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+	<defaultcontrol always="true">155</defaultcontrol>
+	<zorder>0</zorder>
+	<include>dialogeffect</include>
+	<controls>
+		<control type="image">
+			<description>Background fade</description>
+			<width>100%</width>
+			<height>100%</height>
+			<texture>emby-bg-fade.png</texture>
+		</control>
+		
+		<control type="group">
+			<width>715</width>
+			<left>32%</left>
+			<top>20%</top>
+			<control type="image">
+				<description>Background box</description>
+				<texture border="6" colordiffuse="ff111111">white.png</texture>
+				<width>100%</width>
+				<height>525</height>
+			</control>
+
+			<control type="image">
+				<description>Emby logo</description>
+				<texture>logo-white.png</texture>
+				<aspectratio>keep</aspectratio>
+				<width>120</width>
+				<height>49</height>
+				<top>30</top>
+				<left>25</left>
+			</control>
+
+			<control type="label">
+				<description>Please sign in</description>
+				<label>$ADDON[plugin.video.emby 30612]</label>
+				<textcolor>white</textcolor>
+				<font>font12</font>
+				<aligny>top</aligny>
+				<align>center</align>
+				<top>80</top>
+				<width>100%</width>
+			</control>
+
+			<control type="group">
+				<top>100</top>
+				<width>620</width>
+				<height>245</height>
+				<left>50</left>
+				<control type="list" id="155">
+					<description>Select User</description>
+					<focusposition>0</focusposition>
+					<width>100%</width>
+					<top>40</top>
+					<onleft>155</onleft>
+					<onright>155</onright>
+					<ondown>200</ondown>
+					<pagecontrol>60</pagecontrol>
+					<orientation>horizontal</orientation>
+					<scrolltime tween="sine" easing="out">250</scrolltime>
+					<itemlayout width="155">
+						<control type="group">
+							<width>150</width>
+							<control type="image">
+								<description>User image</description>
+								<colordiffuse>ff888888</colordiffuse>
+								<info>ListItem.Icon</info>
+								<aspectratio>keep</aspectratio>
+								<width>100%</width>
+								<height>150</height>
+							</control>
+
+							<control type="image">
+								<description>Background label</description>
+								<texture colordiffuse="ff222222">white.png</texture>
+								<width>100%</width>
+								<height>50</height>
+								<top>150</top>
+							</control>
+
+							<control type="label">
+								<width>100%</width>
+								<align>center</align>
+								<height>50</height>
+								<top>150</top>
+								<font>font10</font>
+								<textcolor>white</textcolor>
+								<info>ListItem.Label</info>
+							</control>
+						</control>
+					</itemlayout>
+					<focusedlayout width="155">
+						<control type="group">
+							<width>150</width>
+							<control type="image">
+								<description>User image</description>
+								<info>ListItem.Icon</info>
+								<aspectratio>keep</aspectratio>
+								<width>100%</width>
+								<height>150</height>
+								<visible>Control.HasFocus(155)</visible>
+							</control>
+							<control type="image">
+								<description>User image</description>
+								<colordiffuse>ff888888</colordiffuse>
+								<info>ListItem.Icon</info>
+								<aspectratio>keep</aspectratio>
+								<width>100%</width>
+								<height>150</height>
+								<visible>!Control.HasFocus(155)</visible>
+							</control>
+
+							<control type="image">
+								<description>Background label</description>
+								<texture colordiffuse="ff333333">white.png</texture>
+								<width>100%</width>
+								<height>50</height>
+								<top>150</top>
+								<visible>Control.HasFocus(155)</visible>
+							</control>
+							<control type="image">
+								<description>Background label</description>
+								<texture colordiffuse="ff222222">white.png</texture>
+								<width>100%</width>
+								<height>50</height>
+								<top>150</top>
+								<visible>!Control.HasFocus(155)</visible>
+							</control>
+
+							<control type="label">
+								<width>100%</width>
+								<align>center</align>
+								<height>50</height>
+								<top>150</top>
+								<font>font10</font>
+								<textcolor>white</textcolor>
+								<info>ListItem.Label</info>
+							</control>
+						</control>
+					</focusedlayout>
+				</control>
+
+				<control type="scrollbar" id="60">
+					<top>100%</top>
+					<width>615</width>
+					<height>5</height>
+					<onleft>155</onleft>
+					<onleft>60</onleft>
+					<onright>60</onright>
+					<texturesliderbackground colordiffuse="ff000000" border="4">box.png</texturesliderbackground>
+					<texturesliderbar colordiffuse="ff222222" border="4">box.png</texturesliderbar>
+					<texturesliderbarfocus colordiffuse="ff222222" border="4">box.png</texturesliderbarfocus>
+					<showonepage>false</showonepage>
+					<orientation>horizontal</orientation>
+				</control>
+
+				<control type="group">
+					<width>615</width>
+					<height>325</height>
+					<top>100%</top>
+					<control type="group">
+						<control type="button" id="200">
+							<description>Manual Login button</description>
+							<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+							<texturefocus border="5" colordiffuse="ff585858">box.png</texturefocus>
+							<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30540][/B][/UPPERCASE]</label>
+							<align>center</align>
+							<width>100%</width>
+							<height>50</height>
+							<top>35</top>
+							<font>font10</font>
+							<textcolor>ffa6a6a6</textcolor>
+							<focusedcolor>white</focusedcolor>
+							<ondown>201</ondown>
+							<onup>155</onup>
+						</control>
+
+						<control type="button" id="201">
+							<description>Cancel</description>
+							<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
+							<texturefocus border="5" colordiffuse="ff585858">box.png</texturefocus>
+							<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
+							<font>font10</font>
+							<textcolor>ffa6a6a6</textcolor>
+							<focusedcolor>white</focusedcolor>
+							<align>center</align>
+							<width>100%</width>
+							<height>50</height>
+							<top>90</top>
+							<onup>200</onup>
+						</control>
+					</control>
+				</control>
+			</control>
+		</control>
+	</controls>
+</window>
\ No newline at end of file
diff --git a/resources/skins/default/media/separator.png b/resources/skins/default/media/emby-separator.png
similarity index 100%
rename from resources/skins/default/media/separator.png
rename to resources/skins/default/media/emby-separator.png
diff --git a/resources/skins/default/media/fading_circle.png b/resources/skins/default/media/fading_circle.png
new file mode 100644
index 00000000..56aea399
Binary files /dev/null and b/resources/skins/default/media/fading_circle.png differ
diff --git a/resources/skins/default/media/network.png b/resources/skins/default/media/network.png
new file mode 100644
index 00000000..7cd11ddf
Binary files /dev/null and b/resources/skins/default/media/network.png differ
diff --git a/resources/skins/default/media/user_image.png b/resources/skins/default/media/user_image.png
new file mode 100644
index 00000000..194a78b1
Binary files /dev/null and b/resources/skins/default/media/user_image.png differ
diff --git a/resources/skins/default/media/userflyoutdefault.png b/resources/skins/default/media/userflyoutdefault.png
new file mode 100644
index 00000000..046676e5
Binary files /dev/null and b/resources/skins/default/media/userflyoutdefault.png differ
diff --git a/resources/skins/default/media/userflyoutdefault2.png b/resources/skins/default/media/userflyoutdefault2.png
new file mode 100644
index 00000000..24d771eb
Binary files /dev/null and b/resources/skins/default/media/userflyoutdefault2.png differ
diff --git a/resources/skins/default/media/wifi.png b/resources/skins/default/media/wifi.png
new file mode 100644
index 00000000..2a646c55
Binary files /dev/null and b/resources/skins/default/media/wifi.png differ
diff --git a/service.py b/service.py
index dbdfa414..0cd245c5 100644
--- a/service.py
+++ b/service.py
@@ -43,7 +43,7 @@ log = logging.getLogger("EMBY.service")
 #################################################################################################
 
 
-class Service():
+class Service(object):
 
     welcome_msg = True
     server_online = True
@@ -59,7 +59,7 @@ class Service():
 
         self.clientInfo = clientinfo.ClientInfo()
         self.addonName = self.clientInfo.getAddonName()
-        logLevel = userclient.UserClient().getLogLevel()
+        logLevel = settings('logLevel')
         self.monitor = xbmc.Monitor()
 
         window('emby_logLevel', value=str(logLevel))
@@ -128,7 +128,7 @@ class Service():
                 
                 # Emby server is online
                 # Verify if user is set and has access to the server
-                if (user.currUser is not None) and user.HasAccess:
+                if user.get_user() is not None and user.get_access():
 
                      # If an item is playing
                     if xbmc.Player().isPlaying():
@@ -166,7 +166,7 @@ class Service():
                             # Reset authentication warnings
                             self.welcome_msg = False
                             # Get additional users
-                            additionalUsers = user.AdditionalUser
+                            additionalUsers = settings('additionalUsers')
                             if additionalUsers:
                                 add = ", %s" % ", ".join(additionalUsers)
                             else:
@@ -174,7 +174,7 @@ class Service():
                             xbmcgui.Dialog().notification(
                                         heading=lang(29999),
                                         message=("%s %s%s!"
-                                                % (lang(33000), user.currUser.decode('utf-8'),
+                                                % (lang(33000), user.get_username().decode('utf-8'),
                                                     add.decode('utf-8'))),
                                         icon="special://home/addons/plugin.video.emby/icon.png",
                                         time=2000,
@@ -194,7 +194,7 @@ class Service():
                             library.start()
                 else:
                     
-                    if (user.currUser is None) and self.warn_auth:
+                    if (user.get_user() is None) and self.warn_auth:
                         # Alert user is not authenticated and suppress future warning
                         self.warn_auth = False
                         log.info("Not authenticated yet.")
@@ -202,10 +202,8 @@ class Service():
                     # User access is restricted.
                     # Keep verifying until access is granted
                     # unless server goes offline or Kodi is shut down.
-                    while user.HasAccess == False:
+                    while not user.get_access():
                         # Verify access with an API call
-                        user.hasAccess()
-
                         if window('emby_online') != "true":
                             # Server went offline
                             break
@@ -218,11 +216,11 @@ class Service():
                 # or Kodi is shut down.
                 while not monitor.abortRequested():
                     
-                    if user.getServer() == False:
+                    if user.get_server() is None:
                         # No server info set in add-on settings
                         pass
                     
-                    elif user.getPublicUsers() == False:
+                    elif not user.verify_server():
                         # Server is offline.
                         # Alert the user and suppress future warning
                         if self.server_online:
@@ -287,7 +285,7 @@ class Service():
         ##### Emby thread is terminating. #####
 
         if self.userclient_running:
-            user.stopClient()
+            user.stop_client()
             
         if self.library_running:
             library.stopThread()