|
a/src/mediaserver/00doc.txt |
|
b/src/mediaserver/00doc.txt |
1 |
= Upmpdcli Mediaserver notes
|
1 |
= Upmpdcli Mediaserver notes
|
2 |
|
2 |
|
3 |
A mediaserver process is create by the main upmpdcli process if a call to
|
3 |
A Media Server instance is created by the main upmpdcli process if a call
|
4 |
ContentDirectory::mediaServerNeeded() returns true, which occurs if the
|
4 |
to ContentDirectory::mediaServerNeeded() returns true, which occurs if the
|
5 |
root directory of the Content Directory would not be empty (see further for
|
5 |
root directory of the Content Directory would not be empty (see further for
|
6 |
how this is determined.
|
6 |
how this is determined).
|
|
|
7 |
|
|
|
8 |
Depending on the command line option, the Media Server can be as a separate
|
|
|
9 |
device/process (the default, which also allows to run a Media Server only,
|
|
|
10 |
with no Renderer device), or as an embedded device of the main Media
|
|
|
11 |
Renderer device. Very few Control Points can handle the latter approach,
|
|
|
12 |
and the code was kept mostly because we can.
|
7 |
|
13 |
|
8 |
== ContentDirectory.cxx
|
14 |
== ContentDirectory.cxx
|
9 |
|
15 |
|
10 |
This file contains the libupnpp callbacks, and the root directory creation
|
16 |
This file contains the libupnpp callbacks, and the root directory creation
|
11 |
code, together with plugin activation and dispatch code.
|
17 |
code, together with plugin activation and dispatch code.
|
12 |
|
18 |
|
13 |
=== Root directory creation
|
19 |
=== Root directory creation
|
14 |
|
20 |
|
15 |
It was originally thought that this module would see several different
|
21 |
It was originally thought that this ContentDirectory.cxx would see several
|
16 |
provider modules, so that it would be natural that it builds the root
|
22 |
different provider modules, so that it would be natural that it builds the
|
17 |
directory (with top entries for each of the modules).
|
23 |
root directory (with top entries for each of the modules).
|
18 |
|
24 |
|
19 |
What happened finally is that there is only one provider module,
|
25 |
What happened finally is that there is only one provider module,
|
20 |
plgwithslave.cxx, so that the root directory creation code, which has
|
26 |
plgwithslave.cxx, so that the root directory creation code, which has
|
21 |
knowledge of its internal should probably moved to plgwithslave.cxx:
|
27 |
knowledge of its internals should probably moved to plgwithslave.cxx:
|
22 |
if other modules were added, we'd need an interface so that they each can
|
28 |
if other modules were added, we'd need an interface so that they each can
|
23 |
provide their entries for the root dir. As it is, things can stay this way.
|
29 |
provide their entries for the root dir. As it is, things can stay this way.
|
24 |
|
30 |
|
25 |
ContentDirectory::makerootdir(), which should be in plgwithslave, uses the
|
31 |
ContentDirectory::makerootdir(), which should be in plgwithslave, uses the
|
26 |
plgwithslave plugins names to create root entries.
|
32 |
plgwithslave plugin names to create root entries.
|
27 |
|
33 |
|
28 |
The plugin names are currently used in quite a few places. For a module
|
34 |
The plugin names are currently used in quite a few places. For a module
|
29 |
named 'plgname':
|
35 |
named 'plgname':
|
30 |
|
36 |
|
31 |
- As the name of a subdirectory under cdplugins, used to enumerate the
|
37 |
- As the name of a subdirectory under cdplugins, used to enumerate the
|
|
... |
|
... |
46 |
(capitalized), with objid 0$plgname$
|
52 |
(capitalized), with objid 0$plgname$
|
47 |
|
53 |
|
48 |
|
54 |
|
49 |
=== Plugin activation and dispatch
|
55 |
=== Plugin activation and dispatch
|
50 |
|
56 |
|
51 |
When browse is requeted for an objectid beginning with 0$plgname$, the code
|
57 |
When the browse method is called for an object Id beginning with
|
52 |
looks up (Internal::pluginforapp(), and possibly creates
|
58 |
0$plgname$, the code looks up (Internal::pluginforapp(), and possibly
|
53 |
(Internal::pluginfactory()) the plugin. Currently, this is always a
|
59 |
creates (Internal::pluginfactory()) the plugin. Currently, this is always a
|
54 |
PlgWithSlave object, created with 2 parameters: the service name (which it
|
60 |
PlgWithSlave object, created with 2 parameters: the service name (which it
|
55 |
will use to find and exec the python module), and a service interface which
|
61 |
will use to find and exec the python module), and a service interface which
|
56 |
it can use to request information from the ContentDirectory.
|
62 |
it can use to request information from the ContentDirectory.
|
57 |
|
63 |
|
58 |
The knowledge that all of a plugin's object ids must begin with 0$plgname$
|
64 |
The knowledge that all of a plugin's object ids must begin with 0$plgname$
|
|
... |
|
... |
79 |
ContentDirectory would just keep a mapping of object id to plugin.
|
85 |
ContentDirectory would just keep a mapping of object id to plugin.
|
80 |
|
86 |
|
81 |
But, things are ok, even if a bit difficult to understand (hence this doc),
|
87 |
But, things are ok, even if a bit difficult to understand (hence this doc),
|
82 |
as long as all providers are modules under plgwithslave.
|
88 |
as long as all providers are modules under plgwithslave.
|
83 |
|
89 |
|
|
|
90 |
libupnp addr and port: these are retrieved in the ContentDirectory init,
|
|
|
91 |
and the plugins get it through service interface calls (getupnpaddr/port())
|
|
|
92 |
The host part is then communicated to the subprocesses through the environment
|
|
|
93 |
(UPMPD_HTTPHOSTPORT), but the port part is not used: the microhttpd port is
|
|
|
94 |
used instead. The port part would have been used if we had been using the
|
|
|
95 |
libupnp miniserver, which we don't because microhttpd can do redirects.
|
84 |
|
96 |
|
|
|
97 |
The uprcl local media server plugin does not use UPMPD_HTTPHOSTPORT at all,
|
|
|
98 |
because it uses its own HTTP server (started on uprclhostport)
|
85 |
|
99 |
|
|
|
100 |
== HTTP server
|
|
|
101 |
|
|
|
102 |
The resource URLs are initially generated by the plugins, and possibly
|
|
|
103 |
transformed when they are actually requested.
|
|
|
104 |
|
|
|
105 |
There are 3 possibilities for the actual HTTP server:
|
|
|
106 |
- The libupnp miniserver (unused at this time)
|
|
|
107 |
- An internal microhttpd instance (only doing redirects at this time)
|
|
|
108 |
- An external server.
|
|
|
109 |
|
|
|
110 |
The 3 streaming service modules (tidal/qobuz/gmusic) currently work in the
|
|
|
111 |
same way:
|
|
|
112 |
- They use the port supplied from plgwithslave which is the one on which
|
|
|
113 |
the microhttpd instance is listening. The URIs are like
|
|
|
114 |
http://host:port/someplugin/xxx.mpr?trackId=tid
|
|
|
115 |
- The microhttpd request method only does redirects. When it gets a
|
|
|
116 |
connection, it parses the above url, passes it to the appropriate plugin
|
|
|
117 |
(as per the path), which provides the actual streaming service URL, to
|
|
|
118 |
which the client is redirected. An hypothetical client which would not
|
|
|
119 |
handle redirects would be out of luck.
|
|
|
120 |
- No configuration data is required (the microhttpd port could be
|
|
|
121 |
configured with plgmicrohttpport, default 49149).
|
|
|
122 |
|
|
|
123 |
The miniserver port value which the plgwithslave constructor receives and
|
|
|
124 |
stores in upnpport is not used, as the miniserver itself
|
|
|
125 |
|
|
|
126 |
The local media server plugin uses a separate http service, which began as
|
|
|
127 |
a completely external server (e.g. an Apache server), but is now more
|
|
|
128 |
conveniently the embedded Python server. The URLS which are generated in
|
|
|
129 |
the directory resource records point directly to the HTTP server host/port,
|
|
|
130 |
there is no redirection through the microhttpd server (this is the big
|
|
|
131 |
difference with the streaming service plugins.
|
|
|
132 |
|
|
|
133 |
The paths still need translation (they include the plugin name as prefix
|
|
|
134 |
for diag purposes, and there is a map between recoll and http paths). The
|
|
|
135 |
translation routine is now part of the Python request handler, but would
|
|
|
136 |
need to be restored if we went back to using an external server.
|
|
|
137 |
|
|
|
138 |
The following configuration data is used:
|
|
|
139 |
The host and port for the HTTP server. This is used in generated URLs
|
|
|
140 |
and as parameter to start the Python server:
|
|
|
141 |
uprclhost = 192.168.4.4:8080
|
|
|
142 |
Paths translation between the Recoll file paths (as used for topdirs)
|
|
|
143 |
and the paths relative to the HTTP server document root, e.g.:
|
|
|
144 |
uprclpaths = /y/av/mp3:/mp3
|
|
|
145 |
The path map was really useful with Apache, much less so with the
|
|
|
146 |
internal server, but has been kept as a way to define the file system
|
|
|
147 |
area allowed for access: any path not in the map will be rejected.
|
|
|
148 |
|
|
|
149 |
In the first implementation, the external HTTP server had to be separately
|
|
|
150 |
configured to serve the media directories. The internal server uses the
|
|
|
151 |
config data.
|
|
|
152 |
|