We have now arrived at the penultimate article in this Add-on Internals series for StationPlaylist add-on: encoder support, the second pillar for the SPL Utilities global plugin. We'll talk about how encoder support is implemented, how NVDA can detect stream labels and a behind the scenes overview of what happens when you connect to a streaming server.
Encoder support: From suggestion to implementation
Originally, I wasn't planning on including encoder support into the SPL add-on. However, after talking to some Studio users who were using SAM encoders and seeing how other screen readers supported it, I decided to investigate SAM encoder support in summer 2014.
The first issue I had to solve was making NVDA recognize the encoder entries themselves. Once that was solved, the next task was announcing connection error messages, which led to figuring out how SAM encoders react when connected to a streaming server.
Originally, I manipulated text written to the screen to obtain needed status messages (via text infos). This routine caused some to experience screen flickering issues when connecting to a streaming server. This was resolved by using encoder description (obj.description), which opened up a possibility to monitor changes to this text via a background thread (more on this routine below), which also eliminated a need to stay on the encoders window until connected.
While I was resolving problems with SAM encoders, I also worked on refactoring encoder support code to support StationPlaylist encoders (add-on 4.0). Initially, encoder support code was optimized for SAM encoders, but the current code structure (explained below) was written to extend basid encoder support easily, and as a result, both SAM and SPL encoder entries present similar interfaces and commands.
Encoder entries: Yet another overlay class family
Just like Studio track items (see the article on track items), encoder entries are overlay classes. Each encoder type (SAM or SPL) inherits from a single encoder object (SPLStudioUtils.encoders.EncoderWindow) that provides basic services such as settings commands, announcing stream labels and so on. Then each encoder type adds encoder-specific routines such as different connection detection routines, ways of obtaining stream labels and so on. Speaking of stream labels and settings, the base encoder class is helped by some friends from the encoder module itself, including a configuration map to store stream labels and basic settings, a routine to obtain encoder ID (encoder string and the IAccessible child ID) and so on.
On top of the base encoder class are two encoder classes, representing SAM encoder entries and SPL encoder entries. SAM encoder entries (SPLStudioUtils.encoders.SAMEncoderWindow) is laid out just like Studio's track items, whereas SPL encoder entries (SPLStudioUtils.encoders.SPLEncoderWindow) is a typical SysListView32 control (see an article on column routines for more information). Both classes provide similar routines, with the only difference being how connection messages are handled.
Common services: basic settings, stream labels and related methods
All encoder classes provide the following common services:
* Configuring settings: three settings can be configured:
A. Pressing F11 will tell NVDA if NVDA snuld switch to Studio when the encoder is connected.
B. Pressing Shift+F11 will ask Studio will play the next track when connected.
C. Pressing Control+F11 will enable background encoder monitoring (more on this in a second).
D. Once these settings are changed, the new values will be stored in appropriate flag in the encoder entry, which in turn are saved in the configuration map.
* Applies settings. This is done by initOverlayClass method - once called, this method will look up various settings for the encoder from the configuration map (key is the setting flag, value is the encoder ID). Another job of this routine is to load stream labels when an encoder first gains focus (if this was loaded earlier, it could be a waste of space, especially if encoders are never used).
* Announces stream labels (if defined) via reportFocus method. In contrast with the Studio track item version, an encoder's reportFocus routine:
A. Locates stream labels for the current encoder (the configuration map stores stream labels as dictionaries (sections), with each dictionary representing the encoder type, key is the encoder position and the value is the label; each encoder, when told to look up stream labels, will consult its own labels dictionary).
B. If a label is found, NVDA will announce the label (in braille, surrounded by parentheses).
* Define and remove stream labels. This is done via stream labels dialog (F12) that'll make sure you entered a label (if not, the encoder position is removed from the encoder-specific stream labels dictionary).
* Updates stream label position when told to do so (via a dialog, activated by pressing Control+F12). This is needed if encoders were removed, as you may hear stream label for an encoder that no longer exists. This is implemented as a variation of find predecessor algorithm.
* Announces encoder columns. The base class can announce encoder position (Control+NVDA+1) and stream label (Control+NVDA+2), while SAM can announce encoder format, status and description and SPL allows one to hear encoder format and transfer rate/connection status.
An encoder ID is a string which uniquely identifies an encoder. This consists of a string denoting the encoder type (SAM for SAM encoder, for instance), followed by the encoder position (separated by a space). For instance, the first SAM encoder is given the ID "SAM 1". The ID's are used to look up stream labels, configure settings and to identify encoders being monitored (SPL Controller, E).
More and more threads: connection messages and background encoder monitoring
As we saw in a previous article, threads allow developers to let programs perform certain tasks in the background. Even in encoder support, threads are employed for various tasks, including connection message announcement and background encoder monitoring.
Each encoder overlay class (not the base encoder) includes dedicated connection handling routines (reportConnectionStatus). Depending on how you invoke this, it starts up as follows:
* If background encoder monitoring is off and you press F9 to connect, NVDA will run this routine in a separate thread. For SAM, this is checked right after sending F9 to the application, and for SPL, this is done after clicking "connect" button (manipulates focus in the process).
* If background encoder monitoring is on before pressing F9, the routine will run from another thread when this setting is switched on. Then when you press F9, NvDA knows that the background monitoring thread is active, thus skipping the above step.
The connection handling routine performs the following:
1. Locates status message for the encoder entry. For SAM, it is the description text, and for SPL, it is one of the entry's child objects (columns). This will be done as long as Studio and/or NVDA is live (that is, if the thread is running).
2. Announces error messages if any and will try again after waiting a little while (fraction of a second).
3. If connected, NvDA will play a tone, then:
A. Do nothing if not told to focus to studio nor play the next track.
B. Focuses to studio and/or plays the next track if no tracks are playing.
4. For other messages, NVDA will periodically play a progress tone and announce connection status so far as reported by the encoder.
5. This loop repeats as long as this encoder is being monitored in the background.
In addition to basic services, each encoder routine has its own goodies, including:
For SAM encoders:
* To disconnect, press F10.
* You can press Control+F9 or Control+F10 to connect or disconnect all encoders (does not work well in recent SAM releases, according to my tests).
For SPL encoders:
* When you press F9 to connect, NVDA does the following:
1. Locates "connect" button, and if it says "Connect", clicks it (obj.doAction).
2. Moves focus back to the entry (self.SetFocus).
* To disconnect, press TAB until you arrive at "Disconnect" button and press SPACE.
We've come a long way: from add-on design and app module contents to encoder support, we discussed internals of a project that makes a difference in lives of many blind broadcasters around the world. I learned many things developing this add-on, and I hope to improve this add-on in the future (which also means publishing future editions of this internals series as new add-on versions are released). To put it all together, let me give you a tour around my lab where I think about, code, test and talk about StationPlaylist Studio add-on for NVDA (and in extension, NVDA screen reader itself).