Monday, June 6, 2011

Background threads and JSON calls

One of the important new features in PseudoTV version 1.2.0 was the use of a background thread to update your channels while you watch them. This meant that, in general, you don't have to wait for the channels to update every time you start PseudoTV...they will just always be up-to-date. Huzzah, right? Yes...kind of.

Since this thing was put in, I've had numerous complaints regarding freezing in the EPG, constant buffering, and general crappiness. Why, oh why do these issues suddenly exist? XMBC hates me, that's why.

Starting a new thread in Python (the scripting language used by XBMC) is easy:

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        # Do stuff here



aThread = myThread()
myThread.start()


Excellent, now we have a thread. That's the easy part. It runs in the background, the main thread still gets called as normal, the world is a wonderful place. Under normal conditions, this thread can be preempted and so it won't block normal operation. Ah, but we run into an issue when we issue a JSON call from inside that thread:

xbmc.executeJSONRPC(command)

This makes a call to an XBMC library function. This function may take up to 15 or 20 seconds to complete. The kicker? This function blocks any Python code from running, even from a different thread. That means no user keypress callbacks, no actions if the user stops the video...no nothing. That doesn't make for a pleasant experience. So, what to do. Thankfully, XBMC provides a webserver that can process JSON calls, and guess what? Since Python isn't directly calling the library function, it won't block the code in other threads. So that's the answer! Kind of. Users don't HAVE to have the webserver turned on. It's off by default...meaning they have to specifically turn it on for it to work. Here's the pleasant function that's required:

def determineWebServer(self):
    if self.discoveredWebServer:
        return

    self.discoveredWebServer = True
    self.webPort = 8080
    self.webUsername = ''
    self.webPassword = ''
    fle = xbmc.translatePath("special://profile/guisettings.xml")
    try:
        xml = open(fle, "r")
    except:
        self.log("determineWebServer Unable to open the settings file", xbmc.LOGERROR)
        self.httpJSON = False
        return
    try:
        dom = parse(xml)
    except:
        self.log('determineWebServer Unable to parse settings file', xbmc.LOGERROR)
        self.httpJSON = False
        return
    xml.close()

    try:
        plname = dom.getElementsByTagName('webserver')
        self.httpJSON = (plname[0].childNodes[0].nodeValue.lower() == 'true')
        self.log('determineWebServer is ' + str(self.httpJSON))

    if self.httpJSON == True:
        plname = dom.getElementsByTagName('webserverport')
        self.webPort = int(plname[0].childNodes[0].nodeValue)
        self.log('determineWebServer port ' + str(self.webPort))
        plname = dom.getElementsByTagName('webserverusername')
        self.webUsername = plname[0].childNodes[0].nodeValue
        self.log('determineWebServer username ' + self.webUsername)
        plname = dom.getElementsByTagName('webserverpassword')
        self.webPassword = plname[0].childNodes[0].nodeValue
        self.log('determineWebServer password is ' + self.webPassword)
    except:
        return

This ridiculous function directly opens up the settings file that XBMC creates, checks to see if the user has enabled the webserver, and grabs the username, password, and port for it. So if this data is detected then that's the method that will be used for JSON calls. If it isn't (self.httpJSON is False) then the normal library call is made.

It's actually pretty amazing how much of the code is just a mix of ways to bypass certain limitations.

Also, please enable your webserver if you use PseudoTV! I don't care what port or what mess of username and password you have...just turn that sucker on.

So there ya go, the mess of the background thread and the insanity required to get it to work at all. I may find some better way to do this in the future, but for the moment this is what we have.

* If you liked this post please click on one of the ads, or donate through paypal to help support the PseudoTV developer

No comments:

Post a Comment