Skip to content

Developing your own plugin

Alexey Yakovenko edited this page Nov 12, 2019 · 10 revisions

Plugin types

We have several plugin types in Deadbeef. As a plugin author, you need to decide which type of plugin you want

Decoder

Reads a source file(s), and converts it to PCM format for playback. Also, decoder is responsible for checking file format (probing), inserting the track (or all subtracks) to playlist, seeking in the file, reading and writing metadata. Some of these features are optional to implement, but all of them are highly desirable from a good decoder plugin. There is a decoder plugin template in the root folder of source distribution. It might be slightly outdated though, but should be very handy to start off.

Output

Accepts PCM input, and plays it back through some sound API. For example ALSA or OSS. Also needs to handle play/pause/stop commands. It is easiest to start off some existing plugin. OSS and NULL output plugins are very simple.

DSP

DSP plugins accept PCM, and do some processing on it. Good example is EQ. I'm sure most people willing to write a DSP plugin already know what to do :)

Misc

Miscellaneous plugins can do anything not directly related to audio and I/O. For example, last.fm scrobbler, converter, GUI extensions, visualizations, etc. Sometimes, they are running in their own thread, sending and receiving signals to and from Deadbeef core, sometimes they are purely event driven. Usually, any plugin which doesn't fit in another category, should be a Misc plugin.

VFS

VFS plugins implement multiple data transfer protocols over a unified interface, which is similar to stdio file API. By default, Deadbeef ships with stdio (normal local files) and vfs_curl (streaming over procols provided by libcurl, such as http, ftp, etc) plugins. This plugin type can be used to implement support for GIO, archive formats, etc. Though it might need some work on the API side. At the time of writing (0.4.2) VFS plugin API only supports open/read/close operation, and some extra functions for http/icy specific stuff.

Playlist

This type of plugins is responsible for loading playlists in different formats. For example, the M3U and PLS format support is implemented using the playlist plugin called "m3u". It should also be capable of handling cuesheets, so you can, for example, create a plugin which will be able to load "tracks+cue" type of cuesheets. Could be a good starting point if you want to make a new useful plugin.

GUI

This type of plugins is almost like the Misc plugins, with few important differences:

  • There can only be one GUI plugin running at the same time, selected using the "gui_plugin" config file option.
  • The GUI plugin start method is executed last, after all plugins have been loaded and connected.
  • It's running on the main thread, and launched directly from "main" function.
  • It has to be named in a special way "ddb_gui_NAME.so", where name is the word which will be shown in the GUI Plugin Selector (see existing GTKUI plugin's Preferences page).

Where to start

Normally, you need to #include <deadbeef/deadbeef.h>, and build your plugin as a shared library, like this:

gcc -std=c99 -shared -O2 -o myplugin.so myplugin.c

The file /usr/include/deadbeef/deadbeef.h should've been installed with the player itself. If not -- look for deadbeef-devel package, or something like this. Or get the file from a source tarball. deadbeef.h has some important comments, so you should read it.

In the beginning of the file, there is description on how to write your plugin entry point function.

This entry point normally should only do one thing: return a pointer to plugin declaration structure. See source code of other plugins to see how it works. It is a very simple concept.

Plugins initialization order

  1. all plugins are loaded using dlopen, and their _load entry point is called (GUI plugin is loaded before everything else, but please don't rely on it);
  2. Optional "start" method is called for each plugin excluding the GUI plugin. A plugin will be unloaded if "start" returns -1;
  3. Optional "connect" method is called for each plugin including the GUI plugin. A plugin will be unloaded if "connect" returns -1;
  4. DB_EV_PLUGINSLOADED message is sent to all plugins;
  5. "gui.start" is called.

Explanation

Many plugins use functionality of other plugins.

To do this, plugins must connect with other plugins. To decide how and when to do it, you need to understand how plugins are loaded and initialized.

  • The order of plugin loading and initialization is undefined. You must never think that your plugin will be loaded before or after some other plugins.
  • The correct place to connect to other plugin is from "connect" method of your plugin.
  • Some plugins can unload if their "connect" returns error, so sometimes it makes sense to initialize your stuff in the DB_EV_PLUGINSLOADED event handler. In this case you will know for sure that you have the final plugin list, and nothing will suddenly disappear.

Use cases for start, connect and DB_EV_PLUGINSLOADED

  1. start: use it for your plugin initialization, e.g. checking system requirements;
  2. connect: use it for detecting if other plugins are available. For example, if you want to use EQ plugin from your plugin -- the connect is the place to check if EQ is available;
  3. DB_EV_PLUGINSLOADED: use this if you must be 100% sure that all plugins are loaded and fully initialized. E.g. if you need to get a function pointer from another plugin, and store it (e.g. hotkeys plugin is doing this).

Backwards compatibility

Deadbeef has stable backwards-compatible API since version 0.5.0.

The API itself is versioned. The API version consists of 2 numbers: Major and Minor version. The change in Major version means that the API is not compatible with the previous version anymore. API versions start from 1.0 and go upwards -- 1.1, 1.2, etc. You can find API version history in deadbeef.h.

It is preferred that all plugins are developed using the latest SDK (deadbeef.h), even if you target older deadbeef releases.

To learn how to make your plugin compatible with current and future releases of deadbeef, you need to understand the concepts of the Target API Version and the API Level.

Setting the Target API Version is the most important (and the only critical) part, and is supported since API 1.0.

Setting the API Level is supported since API 1.5, and it's not critical to do - it just makes the developer's life easier.

You can also get warnings about deprecated functions, if needed. This is also supported since API 1.5.

Setting the target API version

This is the minimum API version that your plugin supports. For example, if you want your plugin to work on deadbeef 0.5.0 and up -- the target API version is 1.0. You can find this from deadbeef.h.

Set your plugin's api_vmajor and api_vminor to the 1 and 0 respectively.

This will tell deadbeef, that the plugin is compatible with deadbeef 0.5 and up, until the API major version changes to anything else than 1.

Setting the API level

The API level is the number corresponding to API minor version, which says "my plugin requires at least version X to build".

By default, it's set to the maximum API version of your deadbeef.h.

You can change it using the DDB_API_LEVEL preprocessor definition, e.g. by adding -DDDB_API_LEVEL=4 to the CFLAGS in your Makefile, you tell the SDK, that your plugin requires the functions up to API version 1.4 (deadbeef 0.5.5), but not higher. After you do that, any functions or data structures, that appeared in deadbeef 0.5.6 or later, will not be available to your plugin.

Getting warnings about deprecation

The deprecation warnings are disabled by default. They can be enabled by setting DDB_WARN_DEPRECATED to 1, e.g. by adding -DDDB_WARN_DEPRECATED=1 to your CFLAGS.

The warnings will tell you which functions are considered deprecated on the selected API level, because better alternatives have been implemented.

What's next?

Read other plugin's source code, come chat with devs on Slack, don't be afraid to ask. We are trying to be as friendly to newcomers as possible, and we are willing to help.