Exploring the Universal Plug and Play Architecture

Install and Configure

To install the BRisa UPnP framework on your computer:

  1. Download the latest BRisa framework package [7].
  2. Decompress the BRisa framework package as root:

    tar zxvf brisa-{version}.tar.gz
  3. Go to the BRisa source code directory:

    cd brisa-{version}
  4. Run the BRisa installation:

    python setup.py install

It is also a good idea to install the BRisa configuration tool, which is available at the BRisa website. The configuration tool will save you from having to edit configuration text files by hand.

After configuring the BRisa media server, you can start it by simply executing the command brisa-media-server on a terminal, regardless of the current directory you are in. Similarly, you can start the BRisa media renderer with the command brisa-media-renderer. The configuration tool allows users to change their media server default parameters and the installed plugins. (Plugins provide additional functionality not found in the default media server. For example, the Filesystem plugin lets you set up filesystem directories in which the BRisa media server will share your audio, video, and image files.)

If the BRisa media server starts correctly, you'll see the message shown in Listing 1. If you don't see this message, check the brisa.log file to see if you can detect the problem. If the problem persists, contact the BRisa team [9] or try the troubleshooting section of the BRisa documentation [10].

Listing 1

BRisa Media Server Startup Message

01 BRisa Media Server version 0.7 started. Please refer to /home/leandro/.brisa/brisa.log for more information.
02 Listen address:

Developing a UPnP Control Point

After you have installed the BRisa UPnP framework, you can use the framework to develop a simple command-line UPnP control point. The control point API lets you create a control point class and inherit from either the brisa.control_point.control_point.ControlPoint or the brisa.control_point.control_point_av.ControlPointAV class, depending on your purpose. The control point example shown in Listing 2 searches UPnP devices, browses for multimedia items in a given media server, and asks for a media renderer to play an audio file shared by the media server.

Listing 2

Command-Line Control Point

01 import sys
02 from brisa.control_point.control_point_av import ControlPointAV
03 from brisa.main import ThreadManager
05 class CommandLineControlPointAV(ControlPointAV):
07     def __init__(self):
08         ControlPointAV.__init__(self)
09         self.subscribe('new_device_event', self.on_new_device)
10         self.subscribe('remove_device_event', self.on_remove_device)
11         self.devices_found = []
13     def on_new_device(self, dev):
14          self.devices_found.append(dev)
16     def on_remove_device(self, udn):
17          for dev in self.devices:
18              if dev.udn == udn:
19                  self.devices_found.remove(dev)
20                  break
22     def cmd_list_devices(self):
23          n = 0
24          for dev in self.devices_found:
25              print 'device %d:' % n
26              print '\tudn:', dev.udn
27              print '\tfriendly_name:', dev.friendly_name
28              print '\tservices:', dev.services
29              print '\ttype:', dev.device_type
30              if dev.devices:
31                  print '\tchild devices:'
32                  for child_dev in dev.devices:
33                      print '\t\tudn:', child_dev.udn
34                      print '\t\tfriendly_name:', child_dev.friendly_name
35                      print '\t\tservices:', dev.services
36                      print '\t\ttype:', child_dev.device_type
37              print
38              n += 1
40     def cmd_set_server(self, id):
41          self.current_server = self.devices_found[id]
43     def cmd_set_render(self, id):
44          self.current_renderer = self.devices_found[id]
46     def cmd_browse(self, id):
47          result = self.browse(id, 'BrowseDirectChildren', '*', 0, 10)['Result']
48          for d in result:
49              print "%s %s %s" % (d.id, d.title, d.upnp_class)
51     def run(self):
52          exit = False
53          try:
54              while not exit:
55                  c = str(raw_input('>>> '))
57                 if c == 'start_search':
58                      self.start_search(600, "upnp:rootdevice")
59                      print 'search started!'
60                  elif c == 'stop_search':
61                      self.stop_search()
62                      print 'search stopped!'
63                  elif c == 'list':
64                      self.cmd_list_devices()
65                  elif c.startswith('browse'):
66                      self.cmd_browse(c.split(' ')[1])
67                  elif c.startswith('set_server'):
68                      self.cmd_set_server(int(c.split(' ')[1]))
69                  elif c.startswith('set_render'):
70                      self.cmd_set_render(int(c.split(' ')[1]))
71                  elif c.startswith('play'):
72                      self.play(c.split(' ')[1])
73                  elif c == 'exit':
74                      exit = True
75                  elif c == 'help':
76                      print 'commands: start_search, stop_search, list, ' \
77                            'browse, set_server, set_render, play, exit, help'
78                  c = ''
79          except KeyboardInterrupt, k:
80              print 'quiting'
82         ThreadManager().stop_all()
84 def main():
85      print "BRisa ControlPointAV example\n"
86      cmdline = CommandLineControlPointAV()
87      cmdline.run()
88      sys.exit(0)
90 if __name__ == "__main__":
91      main()

In line 5 of Listing 2, the class CommandLineControlPointAV inherits from the BRisa class brisa.control_point.control_point_av.ControlPointAV. All available multimedia methods specified by the UPnP AV specification and provided by the class brisa.control_point.control_point_av.ControlPointAV are thus also used by the CommandLineControlPointAV class. This example creates the run() class method that reads continuously from the command line using the Python built-in raw_input() function (line 55).

Depending on the command line typed by the user, the application takes a specific action (lines 57 to 76). The possible actions are associated with the user commands start_search, stop_search, list_devices, set_server, set_render, browse, play, exit, and help.

The start_search and stop_search user commands just delegate a simple call to the respective methods available in the brisa.control_point.control_point_av.ControlPointAV class. When the user types the command list_devices in the prompt, the method CommandLineControlPointAV.cmd_list_devices() (line 22) is invoked. This method prints the list of the UPnP devices discovered since the first time the start_search command was run by the user.

BRisa's UPnP framework provides four important bits of information about the discovered devices: the device's UDN, which is an exclusive identifier for the UPnP device in the network; the device type, which tells whether the device is a media server, a media renderer, or some other UPnP device; a list of services provided by the device; and the Friendly Name, a short, more convenient name for the device.

The set_server and set_render commands specify the media server and media renderer, respectively. While you are using the control point infrastructure of the BRisa UPnP Framework, the BRisa Control Point API can notify your application about any new UPnP device that connects to the network.

In Listing 2, lines 9 and 10, I have used the brisa.control_point.control_point_av.ControlPointAV.subscribe() method to tell the BRisa Control Point API to notify the CommandLineControlPointAV class when a new UPnP device has been discovered and also to notify this same class when a UPnP device has left the network. The notification for both events is performed by the BRisa Control Point API by calling the subscribed callback functions CommandLineControlPointAV.on_new_device() and CommandLineControlPointAV.on_remove_device(). Note that because of Python's inheritance mechanism, the class CommandLineControlPointAV inherits the subscribe() superclass method.

The browse command allows the application to browse a specific item in the media server, and the play command sends a play request to the media renderer. The class method CommandLineControlPointAV.cmd_browse() is invoked when the command browse is typed. The cmd_browse() command makes use of the browse() superclass method to retrieve information on the item from the current media server.

A similar process occurs with the play() superclass method, which makes use of a media renderer previously selected by the user to play the specified multimedia item. Listing 3 shows how to use this UPnP control point.

Listing 3

Using the Command-Line Control Point

01 # python control_point_av.py
02 BRisa ControlPoint example
03 >>> start_search
04 search started!
06 >>> list_devices
07 (A list of devices found is printed here. Look for the type field for each device to determine what you will set as
08 media server and what you will set as media renderer.)
11 >>> set_server 0
12 >>> set_render 1
14 Browse the root folder of the media server
15 >>> browse 0
16     1   Music        object.container
17     3   Pictures     object.container
18    12   Playlists    object.container
19     2   Video        object.container
20 (Exploring the folder 2 - Video)
22 >>> browse 2
23      5   Video Broadcast object.container
25 (Exploring the folder 5 - Video Broadcast)
26 >>> browse 5
27      youtube:7   YouTube   object.container
29 (Exploring the items of the YouTube plug-in folder)
30 >>> browse youtube:7
31 youtube:hDiLH7jmVsU   Around the World   object.item.videoItem.videoBroadcast
32 youtube:B5X5cZ62FGg   Californication   object.item.videoItem.videoBroadcast
33 youtube:DF45X3mJsW   Easily   object.item.videoItem.videoBroadcast
36 (Play the video 'Around the World' in the media renderer)
37 >>> play youtube:hDiLH7jmVsU
38 >>> exit

Developing a BRisa Plugin

The BRisa framework comes with a ready-made media server. Rather than create a new one from scratch, you can adapt the BRisa media server with the use of plugins. Next, I will show you how to develop a simple YouTube plugin for the BRisa media server. A full implementation of a YouTube plugin is provided by the BRisa Project.

The BRisa media server plugin architecture (Listing 4) allows the addition of new plugins based on the abstract class called Plugin. Once you have created your own classes that inherit from the brisa.services.cds.plugin.Plugin class, your class becomes a BRisa media server plugin, and you should provide the implementation for the abstract methods load(), unload(), browse(), search(). The most important methods are the last two, which make the plugin capable of responding to remote browsing or searching for multimedia items.

Listing 4

BRisa Plugin Structure

01 from brisa.services.cds.plugin import Plugin
03 class MyOwnBRisaPlugin(Plugin):
05     def __init__(self):
06          Plugin.__init__(self)
08     def load(self):
09          pass
11     def unload(self):
12          pass
14      def browser(self):
15          pass
17     def search(self):
18          pass

The BRisa UPnP framework provides a configuration API that allows retrieval of values from a configuration .ini--style file. The default configuration file for all parameters used by the BRisa UPnP Framework and its applications is stored in the file ~/.brisa/brisa.conf. Therefore, if you need to store and retrieve configuration parameters, you should use the BRisa configuration API.

BRisa's Content Directory subsystem detects and loads the plugin. The Content Directory also delegates the browsing and searching requests when the BRisa media server receives a request from a remote control point to retrieve the multimedia items shared by any installed plugin.

To deploy your plugin after you finish implementing it, simply create a folder named my_plugin under the directory $PYTHON_DIR/site-packages/brisa/services/cds/plugins and save your plugin class in a file called implementation.py directory. The BRisa media server will automatically load and export the content shared by your plugin through the BRisa Content Directory subsystem. This mechanism is illustrated in Figure 2.

As shown in Figure 2, the plugin can use any kind of storage mechanism to store its contents. When a browsing action arrives from the network (step 1), the BRisa Content Directory subsystem redirects the browsing to the correct plugin (steps 2 and 3).

The plugin properly handles the browse request and returns the multimedia items to the BRisa Content Directory subsystem (step 4). The Content Directory subsystem gets the returned list of multimedia items and formats them into an XML-specific standard known as DIDL (Digital Item Declaration Language) (step 5). DIDL is used to represent complex digital objects [11].

Finally, the BRisa media server gets DIDL-XML content, wraps it up into a SOAP message, and sends it back to the remote control point that formats the output in the device screen. On the basis of this extensible architecture, the BRisa media server offers the deployment of third-party plugins that can share multimedia data from a specific source. For more about plugin development, see Section 10 of the BRisa developer documentation [10].

Listing 4 presents a simple plugin class stub. Note that the class MyOwnBRisaPlugin inherits from the class brisa.services.cds.plugin.Plugin. As an example of a real plugin for the BRisa media server, Listing 5 shows the most important part of the YouTube BRisa media server plugin. In Listing 5, a third-party module is used to retrieve YouTube items from the YouTube website. The youtube_api and youtube_dl modules are imported in lines 1 and 2 and are used to retrieve video information and download the .fla file. Around lines 30 and 33, the class YoutubePlugin inherits the BRisa plugin class (as in Listing 4). A BRisa media server plugin has three important attributes: an id that uniquely identifies a plugin, the name that is a short description of the plugin, and a usage that indicates to the BRisa media server whether it should load the plugin automatically.

Listing 5

BRisa YouTube Plugin

01 from youtube_api import YouTubeClient
02 from youtube_dl import get_real_video_url
03 from brisa.services.cds.plugin import Plugin
04 from brisa.utils import properties
05 from brisa.upnp.didl.didl_lite import VideoBroadcast
06 from brisa import config
08 youtube_video_url = config.get_parameter('youtube', 'videourl')
10 class YouTubeItem(VideoBroadcast):
12     protocolInfo = 'http-get:*:video/flv:*'
14     def __init__(self, id, parent_container_id, namespace, title, description,
15                       duration, author, rating):
16          VideoBroadcast.__init__(self, id,
17                                  parent_container_id=parent_container_id,
18                                  namespace=namespace,
19                                  author=author,
20                                  rating=rating,
21                                  duration=duration,
22                                  title=title,
23                                  name=title,
24                                  description=description)
25          self._uri = get_real_video_url("%s%s" % (youtube_video_url, id))
27     def _gen_uri(self):
28          return self._uri
30 class YouTubePlugin(Plugin):
31      id = "7"
32      name = 'youtube'
33      usage = config.get_parameter_bool('youtube', 'usage')
34      videos = {}
36     def __init__(self, *args, **kwargs):
37          Plugin.__init__(self, *args, **kwargs)
39     def _register_plugin(self):
40          self.ytcontainer = self.plugin_manager.root_plugin.add_container("YouTube", self.id, "5", self)
42     def load(self):
43          self._register_plugin()
44          yt = YouTubeClient('ngR1Q8w0OEk')
45          username = config.get_parameter('youtube', 'username')
46          for video in yt.list_by_user(username):
47              video_info = yt.get_details(video['id'])
48              self.add_item(video['id'], self.ytcontainer.id,
49                                 video_info['title'], video_info['description'],
50                                 video_info['length_seconds'], date,
51                                 video_info['author'], video_info['rating_avg'])
53     def add_item(self, video_id, parent_id, title, description, duration, date, author, rating):
54          item = YouTubeItem(video_id, parent_id, self.name, title, description,
55                                      duration, date, author, rating)
56          self.videos[video_id] = item
58     def browse(self, id, browse_flag, filter, starting_index, requested_count, sort_criteria):
59          if browse_flag == 'BrowseMetadata' and id != self.id:
60              return [self.videos[id]]
61          else:
62              return self.videos.values()

The class method _register_plugin(), line 39, is used to register a plugin folder (formally known as a UPnP media server container) in the browse tree of the BRisa media server. Note that in Listing 3, the browse command is executed three times: browsing folder 0 (root folder), then browsing the video folder (id 2), and then the videoBroadcast folder (id 5), where the YouTube folder plugin is registered.

Line 40 (Listing 5) registers the YouTube plugin folder under the folder VideoBroadcast. Note that the YouTube plugin uses the BRisa RootPlugin to register its folder. The RootPlugin is a special Content Directory subsystem plugin that creates the default containers of the BRisa media server (Audio, Video, and Pictures), as shown in Figure 3.

Each item of the default containers is statically identified by an id. The id for VideoBroadcast is 5, which corresponds to the third parameter passed to the method add_container() provided by the RootPlugin. From line 41 to 50, the plugin method load() loads all the user's uploaded videos and stores them in the list of videos represented by the videos attribute of the plugin. In line 45, the plugin makes use of the BRisa Configuration API. Finally, the method browse is called by the Content Directory subsystem when the user sends a browse request to youtube:7 and returns the list of videos loaded by the plugin (Listing 3).

To identify the YouTube folder Content Directory, the subsystem combines the plugin attributes id and name, which is why the result produced for the YouTube registered folder is youtube:7. This same idea relates to the YouTube shared items, such as youtube:hDiLH7jmVsU, used in the example shown in Listing 3. In this manner, when the Content Directory Subsystem receives a requests for the tuple youtube:hDiLH7jmVsU, it splits in two parts at the ":" character, which permits it to identify the plugin and the requested item specified by the control point.

After you finish implementing your plugin, you must create the directory my_youtube_plugin under the directory $PYTHON_DIR/site-packages/brisa/services/cds/plugins, put your plugin source code in the file implementation.py, and save it under the directory you created.

The BRisa media server will load your plugin automatically, and you can now use the UPnP command-line Control Point example or create a more elaborate control point to browse your shared items using the Canola Media Player. Figure 3 shows a list of videos shared by the YouTube plugin.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Video Streaming

    Thanks to protocols such as UPnP and DLNA, video, audio, and photos are distributed to TVs, gaming consoles, PCs, and smartphones without the need for configuration. Linux as a central media server is a welcome guest.

  • Seamless Integration Intro

    Yes, you can do that with Linux… Even if you want to live in an open source world, it's still full of Windows. This month we study strategies for Windows integration.

  • Qt for Maemo

    Write your own smartphone apps with Qt's toolkit for the Maemo platform.

  • Media Centers

    Beyond MythTV and VDR are media centers that take a new approach – or at least make the beaten track look a little different.

  • Free Media Centers

    Free and commercial media center programs promise streaming HD videos, television, music, picture galleries, and a few extra tricks for Linux PCs and the Raspberry Pi – all from the comfort of your living room.

comments powered by Disqus

Direct Download

Read full article as PDF:

050-056-upnp.pdf  (674.45 kB)