= Upmpdcli Mediaserver notes
A Media Server instance is created by the main upmpdcli process if a call
to ContentDirectory::mediaServerNeeded() returns true, which occurs if the
root directory of the Content Directory would not be empty (see further for
how this is determined).
Depending on the command line option, the Media Server can be as a separate
device/process (the default, which also allows to run a Media Server only,
with no Renderer device), or as an embedded device of the main Media
Renderer device. Very few Control Points can handle the latter approach,
and the code was kept mostly because we can.
== ContentDirectory.cxx
This file contains the libupnpp callbacks, and the root directory creation
code, together with plugin activation and dispatch code.
=== Root directory creation
It was originally thought that this ContentDirectory.cxx would see several
different provider modules, so that it would be natural that it builds the
root directory (with top entries for each of the modules).
What happened finally is that there is only one provider module,
plgwithslave.cxx, so that the root directory creation code, which has
knowledge of its internals should probably moved to plgwithslave.cxx:
if other modules were added, we'd need an interface so that they each can
provide their entries for the root dir. As it is, things can stay this way.
ContentDirectory::makerootdir(), which should be in plgwithslave, uses the
plgwithslave plugin names to create root entries.
The plugin names are currently used in quite a few places. For a module
named 'plgname':
- As the name of a subdirectory under cdplugins, used to enumerate the
plugins on startup.
- As the name of the main plugin Python module, named
cdplugins/plgname/plgname-app.py
- As an element of the object ids belonging to this module (all beginning
with 0$plgname$)
- As an element of the resource URIs generated by the module (all
beginning with /plgname).
On startup makerootdir() walks the cdplugins directory (skipping
pycommon). For each subdirectory, it decides that the module is configured
if there is a variable named 'plgnameuser' in the upmpdcli configuration
file (so even plugins without users should have such a variable).
If the plugin is configured, makerootdir() creates an entry named Plgname
(capitalized), with objid 0$plgname$
=== Plugin activation and dispatch
When the browse method is called for an object Id beginning with
0$plgname$, the code looks up (Internal::pluginforapp(), and possibly
creates (Internal::pluginfactory()) the plugin. Currently, this is always a
PlgWithSlave object, created with 2 parameters: the service name (which it
will use to find and exec the python module), and a service interface which
it can use to request information from the ContentDirectory.
The knowledge that all of a plugin's object ids must begin with 0$plgname$
is spread in multiple places in the C++ and Python code.
=== ContentDirectory service interface
ContentDirectory provides a number of service methods to its
providers. They are defined in cdplugins/cdplugin.hxx:CDPluginServices, an
instance of which is passed to the plugin constructor. See the file for
more details.
=== Notes
At the moment, the interface between ContentDirectory and the plgwithslave
plugin is a bit of a mess:
- There is a lack of interfaces which would be needed if other modules
than plgwithslaves were added.
- There is too much knowledge of the plgwithslave internals in
ContentDirectory.
- Both problems are linked.
- Probably plgwithslave should provide the root entries, and
ContentDirectory would just keep a mapping of object id to plugin.
But, things are ok, even if a bit difficult to understand (hence this doc),
as long as all providers are modules under plgwithslave.
libupnp addr and port: these are retrieved in the ContentDirectory init,
and the plugins get it through service interface calls (getupnpaddr/port())
The host part is then communicated to the subprocesses through the environment
(UPMPD_HTTPHOSTPORT), but the port part is not used: the microhttpd port is
used instead. The port part would have been used if we had been using the
libupnp miniserver, which we don't because microhttpd can do redirects.
The uprcl local media server plugin does not use UPMPD_HTTPHOSTPORT at all,
because it uses its own HTTP server (started on uprclhostport)
== HTTP server
The resource URLs are initially generated by the plugins, and possibly
transformed when they are actually requested.
There are 3 possibilities for the actual HTTP server:
- The libupnp miniserver (unused at this time)
- An internal microhttpd instance (only doing redirects at this time)
- An external server.
The 3 streaming service modules (tidal/qobuz/gmusic) currently work in the
same way:
- They use the port supplied from plgwithslave which is the one on which
the microhttpd instance is listening. The URIs are like
http://host:port/someplugin/xxx.mpr?trackId=tid
- The microhttpd request method only does redirects. When it gets a
connection, it parses the above url, passes it to the appropriate plugin
(as per the path), which provides the actual streaming service URL, to
which the client is redirected. An hypothetical client which would not
handle redirects would be out of luck.
- No configuration data is required (the microhttpd port could be
configured with plgmicrohttpport, default 49149).
The miniserver port value which the plgwithslave constructor receives and
stores in upnpport is not used, as the miniserver itself
The local media server plugin uses a separate http service, which began as
a completely external server (e.g. an Apache server), but is now more
conveniently the embedded Python server. The URLS which are generated in
the directory resource records point directly to the HTTP server host/port,
there is no redirection through the microhttpd server (this is the big
difference with the streaming service plugins.
The paths are in three parts:
- The plugin name as prefix
- A mapping between Recoll (actual) paths and paths as seen by the http
server (document_root equivalent)
- The real tail
The translation routine is now part of the Python request handler, but
would need to be restored if we went back to using an external server.
The following configuration data is used:
The host and port for the HTTP server. This is used in generated URLs
and as parameter to start the Python server:
uprclhost = 192.168.4.4:8080
Paths translation between the Recoll file paths (as used for topdirs)
and the paths relative to the HTTP server document root, e.g.:
uprclpaths = /y/av/mp3:/mp3
The path map was really useful with Apache, much less so with the
internal server, but has been kept as a way to define the file system
area allowed for access: any path not in the map will be rejected.
In the first implementation, the external HTTP server had to be separately
configured to serve the media directories. The internal server uses the
config data.