Skip to content
megawubs edited this page Jan 2, 2013 · 1 revision

Hello, I'll take some time to explain how pyplex knows what to play. This guide will save you some time figuring it all out by yourself.

The __main__.py file makes it possible to do python pyplex on the folder, the code inside this file initates pyplex, starts it (if there are no errors) and runs it. Not so hard yet. The module used here is interface, and the class is pyPlex. In the pyPlex class, there happens a lot that is interesting. On initiation the class preforms a few actions.

def __init__(self, arg):
        self.l = pyPlexLogger('PyPlex').logger
        self.canStart = True
        self.l.info("Starting up...")
        self.omxCommand = self.getArg(arg)
        self.hostname = platform.uname()[1]
        self.server = AvahiLookUp("_plexmediasvr._tcp").servers[0]

From top to bottom: start the logger, set canStart to True, log something, set omxCommand (to start omx with), get the hostname, lookup the plex server.

The next method in the pyPlex class is start(), this method does a lot of interesting stuf, lets take a look:

def start(self):
        """Setting up listners and all other stuff"""
        self.l.info("Setting up listeners")
        self.service = ZeroconfService(name=self.hostname + " PyPlex", port=3000, text=["machineIdentifier=" + self.hostname,"version=2.0"])
        self.service.publish()
        self.duration = 0
        self.queue = Queue.Queue()
        self.xbmcCmmd = xbmcCommands(self.omxCommand, self.server)
        self.udp = udplistener(self.queue)
        self.udp.start()
        self.http = httplistener(self.queue)
        self.http.start()
        if self.udp.error == True or self.http.error == True:
            self.canStart = False

Here are a lot of variables initiated and started. The first being the ZeroconfigService. This service is used to make a Avahi share on the network that'll be recogised as a plex-client. Your server will pick this up and register a new client (you can look at your server's log files when you start pyplex, i'll tell you there is a new client in town) Because there is a new client, all IOS/android devices will now have a tab to choose your player. The next important thing is the self.queu variable. This is needed to communicate between threads. Next xbmcCmmd is initiated, which is the wrapper class for commands like Play, Pause and PlayMeda. This class actually starts playback. Next is the initiation of two important listeners. The udp listener and the http listener.

To understand these two i'll explain a bit about how mobile devices communicate with it's clients. When you select a item to play on a client (with the "play on ...." tab) the device sends a request url to the client like this

http://ipofyourpi:3000/xbmcCmds/xbmcHttp?command=TheCommand()

the http listener listens on the port 3000 and preforms this code when a get request is made:

 def get(self):
        self.l = pyPlexLogger('httplistener').logger
        string = self.get_argument("command")
        front = string.index("(")
        end = string.rindex(")")
        command = string[:front]
        commandargs = string[front+1:end].split(';')
        self.queue.put((command, commandargs))
        self.write("received")

You can see that the the command and it's arguments are put in the self.queue variable, this is the same one that is initiated in the pyPlex.start() method. The udp listener works quit the same, this one listens to Play, Pause, and seek commands on port 9777 and adds them to the queue.

Now that all listeners are set, and there are no errors, it's time to run pyplex. This is done by the run method in the pyPlex class.

def run(self):
        """The heart of pyPlex (can you hear it pounding...?)"""
        self.l.info("Running PyPlex")
        if self.canStart == True:
            try:
                while True:
                    # check if xmbc is running
                    if(self.xbmcCmmd.isRunning()):
                        # update position
                        self.xbmcCmmd.updatePosition()
                    # get the command from the listneners
                    command = self.parseCommand()
                    if command:
                        # read the command and args
                        func, args = command
                        # excecute the command
                        func(*args)
                        # check if pyplex has to stop
                        if(self.xbmcCmmd.shutDown == True):
                            self.stop()
                            break
            except Exception, e:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                self.l.error("Caught exception")
                self.l.error(exc_type, fname, exc_tb.tb_lineno) 
                message = 'There went something wrong in %s'
                if(self.xbmcCmmd):
                    self.l.error(message % 'xbmc')
                    self.l.error(e) 
                    self.xbmcCmmd.Stop("")
                    self.stop()
                    return 0
                if(udp):
                    self.l.error(message % 'udp')
                    self.l.error(e)
                    self.udp.stop()
                    self.udp.join()
                if(http):
                    self.l.error(message % 'http')
                    self.l.error(e)
                    self.http.stop()
                    self.http.join()
                raise
        else:
            self.l.error("Error while starting PyPlex")
            self.stop()

This one is quit hard to understand. First there is a check if pyplex actually can start. If it can it'll start a loop that continues to check for new commands and executes them. I'll also put the self.parseCommand() method up here, cause it's quit necessary.

def parseCommand(self):
        """Get commands from the queue"""
        try:
            command, args = self.queue.get(True, 2)
            self.l.info("Got command: %s, args: %s" %(command, args))
            if not hasattr(self.xbmcCmmd, command):
                self.l.error("Command %s not implemented yet" % command)
            else:
                func = getattr(self.xbmcCmmd, command)
                # Retun the function + it's arguments
                return [func, args]
        except Queue.Empty:
            pass

This method get's the command and it's arguments from the queue, checks if the command is a attribute of self.xmbcCmmnd (the command module) if it is, it returns a list of the function object and it's arguments. The next thing that happens in the pyPlex.run() method is the actual command call. This keeps repeating till pyPlex.stop() is ran, which stoppes all the listeners and breaks the loop in pyPlex.run() method.

I hope this gives you an idea how pyplex works and how commands are passed to it.

Clone this wiki locally