File: development.rst

package info (click to toggle)
pa-dlna 1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 812 kB
  • sloc: python: 7,526; makefile: 27; sh: 14
file content (276 lines) | stat: -rw-r--r-- 10,803 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
Development
===========

.. _design:

Design
------

.. _meta data:

Meta Data
"""""""""

This feature is enabled on a per encoder or per device basis with the
``track_metadata`` option set to ``yes``. It is enabled by default.

When ``pa-dlna`` receives a ``change`` event from pulseaudio and this event is
related to a change to the meta data as for example when a new track starts with
a new song, the following sequence of events occurs:

 * ``pa-dlna``:

   + Writes the last chunk to the HTTP socket (see `Chunked Transfer Coding`_)
     and sends a ``SetNextAVTransportURI`` SOAP action with the new meta data.
   + Upon receiving the HTTP GET request from the device, instantiates a new
     Track and starts a task to run the pulseaudio stream.

 * The DLNA device:

   + Gets the ``SetNextAVTransportURI`` with the new meta data and sends a GET
     request to start a new HTTP session for the next track while still playing
     the current track from its read buffer.
   + Still playing the current track, pre-loads the read buffer of the new HTTP
     session.
   + Upon receiving the last chunk for the current track, starts playing the
     next track.

This way, the last part of the current track is not truncated by the amount of
latency introduced by the device's read buffer and the delay introduced by
filling the read buffer of the next track is minimized.

Asyncio Tasks
"""""""""""""

Task names in **bold** characters indicate that there is one such task for each
DLNA device, when in *italics* that there may be such tasks for each DLNA
device.

  UPnPControlPoint tasks:

    ================      ======================================================
    ssdp notify           Monitor reception of NOTIFY SSDPs.
    ssdp msearch          Send MSEARCH SSDPs at regular intervals.
    **root devices**      Implement control of the aging of an UPnP root device.
    ================      ======================================================

  AVControlPoint tasks:

    ================      ======================================================
    main                  Instantiate the UPnPControlPoint that starts the UPnP
                          tasks.
                          |br| Create the pulse task, the http_server task, the
                          renderer tasks.
                          |br| Create the shutdown task.
                          |br| Handle UPnP notifications.

    pulse                 Monitor pulseaudio sink-input events.
    *maybe_stop*          Handle a ``remove`` pulse event.
    *http_server*         Serve DLNA HTTP requests, one task per IP address.
                          |br| Start the client_connected tasks.
    **renderers**         Act upon pulseaudio events.
                          |br| Run UPnP SOAP actions.
    abort                 Abort the pa-dlna program.
    shutdown              Wait on event pushed by the signal handlers.
    ================      ======================================================

  HTTPServer tasks:

    ==================    ======================================================
    *client_connected*    HTTPServer callback wrapped by asyncio in a task.
                          |br| Start the StreamSession tasks:
                          |br| ``parec | encoder program | HTTP socket``.
    ==================    ======================================================

  StreamSession tasks:

    ====================    ====================================================
    *parec process*         Start the parec process and wait for its exit.
    *parec log_stderr*      Log the parec process stderr.
    *encoder process*       Start the encoder process and wait for its exit.
    *encoder log_stderr*    Log the encoder process stderr.
    *track*                 Write the audio stream to the HTTP socket.
    ====================    ====================================================

  Track tasks:

    ==============        ======================================================
    *shutdown*            Write the last chunk and close the HTTP socket.
    ==============        ======================================================

DLNA Device Registration
""""""""""""""""""""""""

For a new DLNA device to be registered, ``pa-dlna`` must establish the **local**
network address to be used in the URL that must be  advertised to the DLNA
device in the ``SetAVTransportURI`` and ``SetNextAVTransportURI`` SOAP actions,
so that the DLNA device may initiate the HTTP session and start the
streaming. This depends on which event triggered this registration:

  Reception of the  unicast response to an UPnP MSEARCH SSDP.
    The destination address of the SSDP response is the address that is being
    looked for.

    MSEARCH SSDP are sent by ``pa-dlna`` every 60 seconds (default).

  Reception of an UPnP NOTIFY SSDP, broadcasted by the device [#]_.
    The DLNA device can be registered only if the source address of this packet
    belongs to one of the subnets of the network interfaces. That is, the DLNA
    device and the host belong to the same subnet on this interface and the
    local IP address on this subnet is the address that is being looked for.

    The `UPnP Device Architecture`_ specification does not specify the
    periodicity of NOTIFY SSDPs sent by DLNA devices.

Development process [#]_
------------------------

Requirements
""""""""""""

Development:
    * `curl`_ and `ffmpeg`_ are used by some tests of the test suite. When
      missing, those tests are skipped. `curl`_ is also needed when releasing a
      new version to fetch the GitLab test coverage badge.
    * `ffmpeg`_, the `Upmpdcli`_ DLNA Media Renderer, the `MPD`_ Music Player
      Daemon and a running Pulseaudio or PipeWire sound server are needed to run
      the tests of the ``test_tracks`` Python module (otherwise those tests are
      skipped).

      An audio track sourced by ffmpeg is streamed by pa-dlna to the Upmpdcli
      DLNA that outputs the stream to MPD, which in turn outputs the stream to a
      PulseAudio/PipeWire sink created by ``test_tracks``. Monitoring the state
      of this sink allows checking that the audio track does follow this
      path. This scenario may be run at the debug log level with the following
      command::

        $ python -m pa_dlna.tests.test_tracks [EncoderName]

    * `pactl`_ is needed to run the tests that connect to the pulseaudio or
      pipewire sound server. When missing, those tests are skipped.
    * `docker`_ may be used to run the test suite in a pulseaudio or pipewire
      debian container. Follow the instructions written as comments in each of
      the ``Dockerfile.pulse`` and ``Dockerfile.pipewire`` Docker files.
    * `coverage`_ is used to get the test suite coverage.
    * `python-packaging`_ is used to set the development version name as conform
      to PEP 440.
    * `flit`_ is used to publish pa-dlna to PyPi and may be used to install
      pa-dlna locally.

      At the root of the pa-dlna git repository, use the following command to
      install pa-dlna locally::

        $ flit install --symlink [--python path/to/python]

      This symlinks pa-dlna into site-packages rather than copying it, so that
      you can test changes by running the ``pa-dlna`` and ``upnp-cmd``
      commands provided that the ``PATH`` environment variable holds
      ``$HOME/.local/bin``.

      Otherwise without using `flit`_, one can run those commands from the root
      of the repository as::

        $ python -m pa_dlna.pa_dlna
        $ python -m pa_dlna.upnp_cmd

Documentation:
    * `Sphinx`_ [#]_.
    * `Read the Docs theme`_.
    * Building the pdf documentation:

      - The latex texlive package group.
      - Imagemagick version 7 or more recent.

Documentation
"""""""""""""

To build locally the documentation follow these steps:

  - Generate the ``default-config.rst`` file::

      $ python -m tools.gendoc_default_config

  - Fetch the GitLab test coverage badge::

      $ curl -o images/coverage.svg "https://gitlab.com/xdegaye/pa-dlna/badges/master/coverage.svg?min_medium=85&min_acceptable=90&min_good=90"
      $ magick images/coverage.svg images/coverage.png

  - Build the html documentation and the man pages::

      $ make -C docs clean html man latexpdf

Updating development version
""""""""""""""""""""""""""""

Run the following commands to update the version name at `latest documentation`_
after a bug fix or a change in the features::

    $ python -m tools.set_devpt_version_name
    $ make -C docs clean html man latexpdf
    $ git commit -m "Update development version name"
    $ git push

Releasing
"""""""""

* Run the test suite from the root of the project [#]_::

    $ python -m unittest --verbose --catch --failfast

* Get the test suite coverage::

    $ coverage run --include="./*" -m unittest
    $ coverage report -m

* Update ``__version__`` in pa_dlna/__init__.py.
*  When this new release depends on a more recent libpulse release than
   previously:

  + Update ``MIN_LIBPULSE_VERSION`` in pa_dlna/__init__.py.
  + Update the minimum required libpulse version in pyproject.toml.

* Update docs/source/history.rst if needed.
* Build locally the documentation, see one of the previous sections.
* Commit the changes::

    $ git commit -m 'Version 1.n'
    $ git push

* Tag the release and push::

    $ git tag -a 1.n -m 'Version 1.n'
    $ git push --tags

* Publish the new version to PyPi::

    $ flit publish

.. include:: common.txt

.. _Chunked Transfer Coding:
    https://www.rfc-editor.org/rfc/rfc2616#section-3.6.1
.. _Read the Docs theme:
    https://docs.readthedocs.io/en/stable/faq.html#i-want-to-use-the-read-the-docs-theme-locally
.. _Sphinx: https://www.sphinx-doc.org/
.. _curl: https://curl.se/
.. _pactl: https://linux.die.net/man/1/pactl
.. _docker: https://docs.docker.com/build/guide/intro/
.. _`coverage`: https://pypi.org/project/coverage/
.. _flit: https://pypi.org/project/flit/
.. _unittest command line options:
    https://docs.python.org/3/library/unittest.html#command-line-options
.. _latest documentation: https://pa-dlna.readthedocs.io/en/latest/
.. _python-packaging: https://github.com/pypa/packaging
.. _ffmpeg: https://www.ffmpeg.org/ffmpeg.html
.. _Upmpdcli: https://www.lesbonscomptes.com/upmpdcli/
.. _MPD: https://mpd.readthedocs.io/en/latest/user.html

.. rubric:: Footnotes

.. [#] All sockets bound to the notify multicast address receive the datagram
       sent by a DLNA device, even though it has been received by only one
       interface at the physical layer.
.. [#] The shell commands in this section are all run from the root of the
       repository.
.. [#] Required versions at ``docs/requirements.txt``.
.. [#] See `unittest command line options`_.