Documentation/Maemo 5 Developer Guide/Using Multimedia Components/Media Application Framework (MAFW)

Contents

[edit] Introduction

The Maemo platform provides multimedia capabilities through the GStreamer framework. GStreamer takes care of all the low-level multimedia playback details (codecs, formats, protocols, etc.), makes developer's lives really easy, and allows rapid development of typical multimedia applications.

However, there are still a few features that are not covered by frameworks like GStreamer. Usually, these topics have to do with the complexity of latest generation Media Player applications, which tried to provide integration with all kinds of multimedia services (UPnP, Last.Fm, Youtube, etc), although others are more traditional, you will find no support in relatively low-level frameworks such as GStreamer for things like playlist manipulation.

The Multimedia Applications FrameWork (MAFW) complements GStreamer to enhance the multimedia experience in the Maemo platform, providing developers with a flexible, easy to use, and extensible high-level layer on top of other included multimedia-related technologies. Specifically, MAFW intends to provide multimedia application developers with:

  • An extensible, pluggable framework interface, which allows you to develop new plugins which provide integration with new multimedia services or rendering engines, and which will seamlessly integrated into all MAFW based applications.
  • Easy to use APIs that speed up application developing, providing support for playback control, discovery and browsing of multimedia resources and playlist manipulation.
  • An independent technology. Since MAFW is a plugin based framework, it is not tied to a particular multimedia technology (GStreamer, MPlayer, and Tracker). Plugin developers have the freedom to choose the technologies they want to use in their plugins, and application developers do not need to care about them.

The purpose of this manual is to introduce the basic concepts of the MAFW framework to interested multimedia developers, explaining its fundamental concepts and providing insight and examples on how it should be used to develop multimedia applications in the Maemo platform.

[edit] Pre-requisites

The examples given in this section are written in C using GLib to interact with MAFW which is built upon the GObject framework. The reader will need to be familiar with these subjects in order to understand these examples.

[edit] Examples

Source code examples are available in the code example section and are intended to illustrate many of the concepts explained in this manual. References to sections with examples are be provided as needed.

[edit] Developing multimedia applications using MAFW

[edit] Main concepts

Here follows a brief explanation on some of the fundamental concepts concerning the MAFW framework. Most of them will be covered in more detail in later sections of this manual:

  • Renderer: An element capable of consuming multimedia content and controlling media playback.
  • Source: An element that provides access to multimedia content such as the media stored in the local filesystem, UPnP servers, Samba shares, Bluetooth devices, Internet services, etc...
  • Object Identifier: Sources identify multimedia resources using object identifiers. Each individual resource has one unique object identifier associated that is provided by the source serving it.
  • Shared playlist: A sorted list of multimedia resources ready to be consumed by a renderer, which can be used and manipulated by several applications in parallel. Playlists are populated with media items extracted from Source components, and assigned to a renderer to be played.
  • Extension: An element that implements a particular task by following a well-defined interface. Currently, two types of extensions are allowed; those that implement new Source components and those that implement new Renderer components. Also, extensions can be used in two manners:
    • In-process extensions: Those that live in the same address space as the application that uses them.
    • Out-of-process extensions: Those that live in a separate process other than the application's. The benefit of this type of extension is that programming flaws in the plug-in code that lead to crashes or freezing of the extension, do not crash or freeze the main application. The main drawback of this approach is the need for inter-process communications to communicate between the application and the extensions, which may have a performance hit and require some extra synchronization effort.
  • Plugin: A plugin is a shared object file implementing a particular Source or Renderer type of extension. For example, in the case of a UPnP source plugin, the shared file implementing the source is the plugin. However, the plugin may instantiate several source extensions, one per UPnP server discovered.
  • Wrapper: In order to make inter-process communications in case of out-of-process plugins, transparent for application developers, all components that can live in a separate processes (Extensions and Shared playlists), are wrapped with local objects that handle the inter-process communication transparently. Thanks to these wrapper objects, in most situations, application developers do not need to know if they are using in-process or out-of-process components. Although wrappers play an important role in MAFW, they are only interesting for developers interested in hacking the internals of the framework. Multimedia application developers do not need to know about them.

[edit] Packages

MAFW is formed by the following packages:

  • MAFW Library: The MAFW library contains the basic classes and interfaces defined in the framework. These expose the major elements of the framework, like the concepts of Extension, Source and Renderer and the APIs the developers will use to interact with them.
  • MAFW Shared: This package provides means of share components among different applications using MAFW. It implements the logic behind the wrapper objects, as well as the concept of Shared playlists. It is composed of:
    • libmafw-shared: A library to use the shared components exposed by sharing services (extensions and shared playlists).
    • mafw-dbus-wrapper: This service is the one responsible for loading extensions in separate processes and export their interfaces as D-Bus services. These services are used by the wrapper objects defined in libmafw-shared in the out-of-process use case.
    • mafw-playlist-daemon: A daemon that provides an API for shared playlist manipulation and storage.
  • Extensions: Extensions are provided as separate packages.

[edit] Getting started

MAFW is available as Debian packages, so it can be installed using tools like dpkg or apt.

MAFW can also be built from source. To build and install a MAFW package from source, move to the root directory of the source and build a MAFW package:

./autogen.sh
make
make install

When building from source, one can also pass certain flags to the autogen.sh script. Check the configure.ac file for a list of available build flags for the module. Common and useful supported flags among all MAFW packages are:

  • --prefix=PATH, installs MAFW files into PATH.
  • --disable-tests, disables tests (speeds up compilation time).
  • --enable-debug, compile with debug options enabled. Useful for debugging.

Also, when building MAFW from source, one should take into account the source dependencies. MAFW should be built in this order:

  1. mafw
  2. mafw-shared
  3. source and renderer plugins

[edit] Initial preparations

Before running MAFW applications, one should ensure the build environment is properly setup.

The first thing that you should check is the playlist daemon. This manual will cover the playlist daemon in more detail in later sections, but for now it is enough to know that this daemon provides functionality for applications to access and manipulate playlists.

In order to run the playlist daemon, one should execute the following command:

mafw-playlist-daemon -d

Also, if you decided to use out-of-process plugins, you must be sure these plugins are running (remember that out-of-process extensions live in separate processes).

There is a tool to load out-of-process extensions called mafw-dbus-wrapper. In order to load a particular plugin in out-of-process mode, do the following:

mafw-dbus-wrapper PLUGIN &

This command will load the specified plugin, searching for it in the MAFW plugin directory. For this purpose, mafw-dbus-wrapper also accepts absolute paths to plugin files:

mafw-dbus-wrapper /home/user/mafw-plugin/bar-mafw-plugin.so

Usually, one would not invoke mafw-dbus-wrapper directly. Instead, it is recommended to create an init script to start all the plugins and the playlist daemon at boot time. Something like this:

/etc/init.d/mafw [start|restart|stop]

As with many other Maemo components, take into account that running commands in a Scratchbox environment requires additional environment preparations. Particularly, the Maemo environment has to be running, and then any shell commands must be run using the run-standalone.sh script, so they are get appropriate environment settings: Environment setup

export DISPLAY=:2
sb-fremantle-init.sh start
run-standalone.sh mafw-playlist-daemon -d
run-standalone.sh mafw-dbus-wrapper PLUGIN &

[edit] Application initialization

As MAFW is based on GObject, the first step in writing a MAFW-based application is to initialize the GType system. You can do this by invoking g_type_init at the beginning of your program.

Of course, this step is not necessary if the developer is using another GObject-based framework, like GTK+. In this case, initializing that framework already initializes the GType system.

The next step is to check for available extensions (sources and renderers). This process may differ depending on whether you are using in-process or out-of-process extensions.

[edit] Loading extensions

This section explains how to load MAFW extensions in a program. The example in the #Code examples section provides complete source code examples illustrating the entire process.

[edit] Loading out-of-process extensions

As we already pointed out, with the out-of-process approach, extensions are loaded by the mafw-dbus-wrapper in a separate address space and they are shared by all applications, which use these extensions through D-Bus services. In order to use these out-of-process extensions, the first step is to discover their availability via the bus. This is done by invoking mafw_shared_init after getting a reference to the registry object with mafw_registry_get_instance. Once the discovery has been started, your application will be notified of newly available extensions through signals. Likewise, when a certain extension is no longer available, a signal is sent to the application so that it can properly handle this.

The MafwRegistry object is a singleton instance where all the available extensions are registered. It provides an interface for application developers to access the available sources and renderers. In the case of out-of-process extensions, the registry will provide proxies to the actual remote extensions that handle the D-Bus communications transparently for you.

It is also possible that extensions were loaded in the registry before the application attached its signal handlers. In this case, the application will not get any signals for these extensions and has to query the registry for them using mafw_registry_get_renderers and mafw_registry_get_sources functions. Please note that you should not free these lists of available extensions.

[edit] Loading in-process extension

Contrary to the out-of-process approach, in-process extensions are loaded in the same address space of the application's process. In this case we do not have to start a discovery process. Instead, we can either load all the available plugins or load specific plugins. As in the out-of-process case, this is done through the MafwRegistry object.

[edit] Loading all available extensions

After getting a reference to the registry, one should call mafw_registry_load_plugins.

[edit] Loading extensions manually

Individual extensions can be loaded manually by using mafw_registry_load_plugin and declaring the plugin to load.

The second parameter of mafw_registry_load_plugin can be just a filename (in this case, the default plugin directory will be used) or the absolute path of the plugin file.

[edit] Being aware of loaded extensions

As in the out-of-process approach, the application has to be informed of loaded extensions. The same code as for the out-of-process case can be used here.

[edit] Querying available extensions after loading

This approach loads the extensions in the registry first and then queries it for the lists of available renderers and sources using mafw_registry_get_renderers and mafw_registry_get_sources.

[edit] Using signals and callbacks

This approach is more asynchronous and it is recommended because it can easily handle sources or renderers added after the application was started. It is done by connecting callbacks to the source-added and renderer-added signals. As soon as a plugin is loaded, a signal is emitted and the application can react to it, preparing everything to use that extension.

[edit] In-process versus out-of-process extensions

There are a few issues to consider when making the decision of using in-process or out-of-process extensions:

  • In-process extensions do not need inter-process communications, speeding up certain operations.
  • Out-of-process extensions use multiple processes and D-Bus (which serializes and integrates these communications nicely with the main loop) avoiding the need of threads in the main application in most cases. In the in-process case, developers may need to use threads or idle calls to execute certain extension operations in order to avoid blocking the user interface while the extensions work (for example source browsing).
  • In the in-process case, signals coming from extensions are normal GObject signals. However, in the out-of-process case, these signals have to be sent through D-Bus. For developers, this means that callbacks for these signals are not invoked as soon as the signal is emitted, but as soon as the D-Bus message is received and processed by the main loop.

[edit] Playlists

This section explains how to use playlists in a program. During this explanation, it is recommended to follow along with the #Playlist example section for a complete source code example illustrating most of the concepts explained below.

[edit] The playlist daemon

MAFW playlists are shared, this means they are stored and exposed by the mafw-playlist-daemon, which makes them available for all MAFW-based applications, that can access and manipulate them concurrently through its API.

Because the playlists are always remote objects stored and handled by a separate process, whenever the application needs to deal with a playlist, there is a need for inter-process communication. However the communication is hidden from developers by using a proxy object. These proxy objects allow developers to interact with the playlists as if they were local objects, making the underlying communications with the playlist daemon transparent for the application developer.

Operations like creating or removing a playlist or obtaining the list of available playlists, are done through the MafwPlaylistManager object. This object acts as a proxy of the playlist daemon for playlist-related operations that do not deal with a particular playlist. A reference to this object can be obtained using mafw_playlist_manager_get.

The following sections explain how to use the MafwPlaylistManager and proxy playlist objects in more detail.

[edit] Creating playlists

The method mafw_playlist_manager_create_playlist is responsible for requesting the creation of a new playlist from the playlist daemon. If the operation is successful, it returns a reference to a MafwProxyPlaylist that will act as a local representation of the remote playlist created in the playlist daemon. If the requested playlist name has already been used (that is, another playlist with that name already exists), no playlist is created and a reference to the already existing playlist is returned. In order to check if a certain playlist already exists, the developer can use mafw_playlist_manager_get_playlists or mafw_playlist_manager_list_playlists to list the playlist names that have been registered already.

It is important to consider the shared nature of the MAFW playlists. From the developer point of view, you should never assume that a certain MAFW-based application is the only one interacting with a playlist.

The application can install a signal handler in order to be notified of the creation of new playlists. This can be done by connecting to the playlist-created signal.

Although new playlists can be used right away after calling mafw_playlist_manager_create_playlist, it is better to handle newly created playlists through the playlist-created callback, because this will work also for playlists created by other MAFW-based applications.

It is possible to import an existing playlist file or the contents of a particular container of a source extension as a MAFW playlist with the help of mafw_playlist_manager_import. If this method is passed a URI as parameter, it will try to parse the file pointed by that URI and populate a playlist with the entries of the playlist file. Likewise, if a source object identifier is used instead of a URI, and it points to a valid source container, the container will be browsed, and the contents will be used to populate the playlist. If the object identifier points to a single media item instead of a container, the associated media will be treated as a playlist file, parsing the file it points to and adding its entries to the playlist.

After a playlist has been imported, the framework will invoke the callback passed as parameter to mafw_playlist_manager_import, with a reference to the imported playlist proxy object. The name of the playlist will be the URI pointing to the file, or the source container title. If a playlist with this name existed already, then a number will be added after the name as a suffix to ensure a unique name is assigned. After this, the MafwPlaylist::playlist-created signal will be emitted.

[edit] Removing playlists

Playlist deletion is achieved through the MafwPlaylistManager object in a similar manner as the creation of new playlists.

The first step is to obtain a reference to the playlist that has to be removed. After a reference has been obtained, mafw_playlist_manager_destroy_playlist is ready to remove it.

If playlist removal was not possible, the playlist manager can also emit the signal playlist-destruction-failed. In general, this happens when trying to remove playlists that are being used somewhere else in the application or when they are being used by some other application.

When an application wants to mark a playlist as being in use, it should call mafw_playlist_increment_use_count. When an application tries to remove a playlist which reference count is greater than zero, the operation will fail. Renderers should always increment the reference count of a playlist when it is assigned and decrement when it is unassigned.

As in the case of playlist creation, it is also a good idea to install a signal handler for playlist deletion so the application can deal with this situation properly. For example, another MAFW-based application may attempt to destroy a playlist that your application is showing as available to the user in a playlist selection widget.

[edit] Handling playlist elements

[edit] Inserting items

Adding elements to the playlist is done by using mafw_playlist_insert_item and specifying the index where the item should be inserted.

A signal will be emitted whenever a playlist is modified. Notice that other MAFW-based applications may be modifying the playlists in use by your application at any moment. By connecting to these signals, your application should be able to handle this situation properly.

This MafwPlaylist::contents-changed signal is used to signal changes of any kind. The from parameter is the index of the first element affected by the change, nremoved is the number of removed elements and nreplaced represents the number of elements replaced. For example, in case of insertion, the signal will be emitted with the insertion index as from, 0 as nremoved and 1 as nreplaced. More information is available in the MAFW API reference.

Also, the convenience function mafw_playlist_append_item can be used to append items to the tail of a playlist.

[edit] Removing items

As in the case of insertion, one only needs to call the method on the playlist proxy object with the index of the element to remove.

The signal received when modifying the playlist is the same as when removing elements. In this case, nremoved would be 1 and nreplaced would be the difference between the number of elements in the playlist and the index of the removed element plus the number of items that were removed (1). There are more examples available in the MAFW API reference.

[edit] Moving items

Moving one element from one index to another can be done by calling mafw_playlist_move_item.

The example moves the element at position 3 to position 1, moving elements in positions 1 and 2 forward to positions 2 and 3. When invoking this method, the signal item-moved is emitted.

[edit] Getting information from playlists

[edit] Synchronously without metadata

Developers traverse a playlist with a loop after obtaining the size of the playlist with mafw_playlist_get_size. For each element in the playlist one can use mafw_playlist_get_item to obtain the object identifier assigned to that entry. In order to obtain information other than the object identifier, the asynchronous API must be used.

[edit] Asynchronously with metadata

In this case, mafw_playlist_get_items_md should be used, which allows you to obtain metadata for the entries enclosed in a particular range of the playlist. This method requires a callback function that will be invoked once per entry in the playlist. The callback is passed a hash table with metadata associated to the media resource associated to the object identifier specified by object_id. This hash table may contain metadata with multiple values for each metadata key and this is why MAFW provides mafw_metadata_first to access the first (or only) value in the hash table for a particular metadata key. In order to obtain all the values for a particular metadata key, the developer can use the normal hash table API. In this case, if there is only one value for that metadata key, the hash table will return a GValue holding the value. If it is multi-valued, it will return a GValueArray. Take a look at the MAFW API reference for more info.

[edit] Shuffle and unshuffle

When a playlist is created, it is unshuffled, which means that playing its contents in a renderer would play the items in the playlist in the same order they were inserted.

It is also possible to shuffle the elements in the playlist using mafw_playlist_shuffle, changing the default playing order.

The method mafw_playlist_get_starting_index can be called to obtain the index and object identifier of the first item of the playlist according to the playing order. The methods mafw_playlist_get_next and mafw_playlist_get_prev can be used to move to the next and previous elements respectively.

Because playlists are played in renderers, the only way to obtain information on which item of a playlist is being played is to obtain it from a renderer object to which the playlist has been assigned. The MafwRenderer::media-changed signal is emitted when the selected item in the playlist assigned to a particular renderer changes.

The application developer can also query the currently selected entry in the playlist assigned to a renderer using the method mafw_renderer_get_status. See the MAFW API reference for details.

[edit] Sources

This section explains how to use sources in a program. During this explanation it is recommended to follow the source browse example and the source metadata example for complete source code examples illustrating most of the concepts explained below.

[edit] Introduction

Sources are used to get access to multimedia repositories. For example, a source might provide access to UPnP servers, the multimedia files located at the local filesystem, or those housed at Bluetooth devices, etc. The main target of sources is to provide a way to query and retrieve information about available media files.

Sources identify media items by means of object identifiers. These identifiers are strings in the form:

<Source-ID>::<Item-ID>

consisting of the Source ID of the originating source and a unique (within the individual scope of the source) Item ID, separated by two colons. Each object ID should be unique, persistent and portable, if possible. Applications may assume that an object ID corresponds to a single content item, and this should be maintained across shutdowns, reboots and reinstalls. A specific object ID is meaningful only for the source that has produced it.

Sources provide a few services to applications, however, the most important are:

  • Browsing
  • Metadata retrieval

Browsing is the operation used to explore, query and filter contents from a particular source of media. Developers will use the browse interface, for example, to get a list of the songs or videos available, to access the songs of a particular artist, etc. Sources provide access to their contents exposing them through the browse interface using a container model: there is a root container, which object ID is <Source-ID>::, any container can contain other containers or media resources, very much like navigating a file system. The structure of containers exposed by the source is decided by the source developer.

Metadata retrieval is the operation used to obtain meta-information from a certain media resource. Developers will use this interface to obtain information like the title, artist or album of a particular media.

In addition to getting content information, one can also modify the contents with optional methods, intended for creating and removing contents from the source, as well as updating metadata for particular media items.

Sources are represented by the MafwSource class, which inherits from MafwExtension. Thus, sources have name, uuid and plugin properties and support run-time properties too. This class is intended to be an abstract base class for any source and it provides the definition of various of the interfaces commented above: mafw_source_browse, mafw_source_get_metadata, mafw_source_set_metadata, etc.

Application developers will use sources to browse available media resources and select media items to create playlists. Once a playlist has been created it can be assigned to a renderer to play its contents.

Sources can also communicate certain events using signals:

  • MafwSource::metadata-changed, informs about metadata changes in a particular media provided by a given source.
  • MafwSource::container-changed, informs about changes in the contents of a certain container provided by a given source.

[edit] Browsing the contents of a source

The major use case of a source is browsing. Developers can explore the contents of a particular container exposed by the source using the method mafw_source_browse and the object identifier of the container. The list of parameters is:

  • Source, the MafwSource instance to be browsed.
  • ObjectID, the object identifier of the container to be browsed.
  • Recursive, whether the container should be browsed recursively or not.
  • Filter, specifies an optional filter string in a syntax similar to that used in LDAP search filters.
  • Sort criteria, a string used to specify the criteria that should be followed to sort the browse results.
  • Keys, metadata keys we are interested in retrieving along with each object identifier returned with the browse operation.
  • Offset, the index of the first item to retrieve from the list of items that match the browse query.
  • Offset, the maximum number of elements to retrieve from the list of items that match the browse query, starting at the index specified by the Offset parameter.
  • Callback, this callback function is called whenever a matching result is found.
  • Callback user data, user data for the callback.

For more details, please check the MAFW API reference.

The mafw_source_browse function returns a browse identifier that can be used to match browse results with the corresponding browse operations.

The results of the browse call are returned using the provided callback. This callback is invoked each time a matching result is found by the source. Each time the callback is invoked it informs about:

  • Source, the MafwSource that is producing the result.
  • Browse ID, that allows the developer to match a result with a particular browse operation.
  • Remaining, which informs about the remaining items that have matched the browse query and are pending to be sent through the callback. When this value reaches 0 it means that all the results have been sent and the operations has finished.
  • Index, the index of the current item in the list of matched results.
  • Object ID, the object ID of the current item.
  • Metadata, the metadata values associated to the current item.
  • User Data, user data of the callback.
  • Error, a GError that, if set, informs of an error in the browse operation.

Also, in case that no results match the browse operation, the callback is invoked once, with the object ID parameter set to NULL.

[edit] Getting metadata

Another important use case of a source is to provide metadata about particular media. This can be achieved using mafw_source_get_metadata. This function queries metadata for a particular object ID (which should be valid for the source being queried, probably returned by a previous browse operation). The parameters passed to mafw_source_get_metadata are:

  • Source, the MafwSource to be queried.
  • Object ID, the object ID to get metadata from.
  • Keys, metadata information we are interested in.
  • Callback, the callback used to get the results.
  • User data, the user data for the callback.

The result of the metadata operation is returned using the callback provided. The parameters passed to the callback function are:

  • Source, the MafwSource that produced the result.
  • Object ID, the object ID the metadata comes from.
  • Metadata, the metadata values.
  • User data, the user data for the callback.
  • Error, a GError that, if set, informs of an error in the metadata retrieval operation.

This callback should be called only once.

[edit] Using a source

Sources are extensions, therefore, the first step is to obtain a reference to them using the framework registry. Please see the section on loading extensions for more information on this topic.

Once a reference to the source of interest has been obtained, the developer can start to use it. The way sources are used is usually this:

  • User wants to browse the contents of the source. The application should browse the root container of the selected source using mafw_source_browse and show the results to the user. The application developer should request the metadata MAFW_METADATA_KEY_MIME to distinguish containers from media items.
  • Once users have obtained the results of the browse operation, they may want to browse one of the containers contained in the root container, in this case the application should issue a new browse operation with the Object ID of the selected container, repeating the process.
  • Also, users may want to select browse results and include them in a playlist to be played later on in a renderer.
  • The user can also request more metadata from a specific item obtained in the browse operation, to do so, the developer should use mafw_source_get_metadata passing the Object ID of the selected item.

For more information about the source API, please check the MAFW API reference. The reader can also check the source browse example and the source metadata example.

[edit] Renderers

This section explains how to use renderers in a program. During this explanation it is recommended to follow section #Renderer example for a complete source code example illustrating many of the concepts explained below.

[edit] Introduction

Renderers are used to control media playback, and they are designed so that any of them can play content from any source, and can be thought of as a GStreamer chain, although this might usually not be the actual case.

MafwRenderer is a subclass of MafwExtension, so, it inherits its semantics. Thus, renderers have name, uuid and plugin properties and support run-time properties too. This class is intended to be an abstract base class for any renderer and it provides various playback-related operations: mafw_renderer_play, mafw_renderer_stop, mafw_renderer_pause, mafw_renderer_resume, etc. that any application can use to control media playback in a particular renderer.

An application can assign a playlist to a renderer. When this is done, the renderer will take over the responsibility for managing it properly, saving some effort from the application developer. For example, the renderer can detect an "end of stream" situation and move automatically to the next item in the playlist, it can readjust automatically when the playlist is being edited, rewind the playlist automatically when the end of the playlist has been reached or even restart playback if the user has enabled the repeat mode in the playlist.

The application can also control playlist playback, commanding the renderer to move to any item in the playlist at any moment.

Whenever a relevant change happens in the renderer, it notifies the application about it, so it can react properly. For example, if a renderer stops playing it notifies about its state change, so that the application can enable and/or disable options according to this situation. Renderers must emit signals when their state, their playlist or the media they are ready to play change.

Renderers also emit signals to inform about buffering progress (MafwRenderer::buffering-info) in the case of media streams, and metadata in the case the renderer is able to extract metadata from the media it is playing.

[edit] State management

Renderers behave like state machines. Operations may or may not be valid depending on the renderer's state, or may change their behavior depending on it. For example, a STOP command may be ignored if the renderer is already stopped, but otherwise it should stop any ongoing playback.

The renderer's state machine has four states:

  • Stopped
  • Transitioning
  • Playing
  • Paused

A renderer is Stopped if it is not playing any media. Whenever the renderer receives the order to play some media content, it moves to the Transitioning state. In this state, the renderer attempts to get anything it may need to play the selected item, for example, it should get the URI of the item to be played by invoking mafw_source_get_metadata, and then use this URI to start playback using the underlying playback engine (for example, GStreamer). The renderer moves to the Playing state once playback has been started by the underlying playback engine. During this state, the user can call mafw_renderer_pause, which makes the renderer move to Paused state. In this state, it is possible to call mafw_renderer_resume to continue playback. Also, the user may call mafw_source_stop at any time, making the renderer move back to the Stopped state again.

Application developers should react to state changes in the renderer and update the application accordingly. For example, when the renderer is in the Stopped state the developer may want to disable the Pause button, however if the renderer state is Playing it may be enabled.

Renderers inform about their state changes using the MafwRenderer::state-changed signal. Application developers should connect to this signal and place any state related management code there. It is also possible to query a renderer about its state by using mafw_renderer_get_status.

[edit] Assigning media to a renderer

In order to play content in a renderer, a playlist should be created and assigned to it. For this purpose developers have to use mafw_renderer_assign_playlist. Once a playlist has been assigned to the renderer the user may choose to start playback right away (starting from the first item in the playlist) or choose a specific item to start from. For the latter case, the renderer offers the methods mafw_renderer_next, mafw_renderer_previous and mafw_renderer_goto_index, that allow the user to select the next and previous items to the one currently selected or move directly to a particular item in the playlist. Also, the currently selected media may change due to other factors, for example, when playback of the current item in the playlist finishes the renderer will move to the next automatically, it might also happen in the case of errors that do not allow to play the current item, in this scenario the renderer may choose to automatically move to the next item in the playlist too.

No matter the reason that has triggered a change in the current media selected in the renderer, it will notify the application about the change by emitting a MafwRenderer::media-changed signal. Application developers should connect to this signal to update the application accordingly whenever new media is selected in the renderer. It is also possible to query a renderer about its current media by using mafw_renderer_get_status.

[edit] Error management

The renderer API functions always receive a callback as parameter. These functions include a GError parameter that is set in case an error happened during the execution of the operation. Developers should provide callback functions and check this error parameter to handle error conditions properly. If the error parameter is NULL it means the operation was executed successfully.

There are certain errors that may happen after the callback has been called. For example, a call to mafw_renderer_play may have finished without error, but after some time the underlying playback engine may detect an error (corrupted data, connection lost in the case of streams, etc). Because of this, there is another channel for error communication, in the form of signals. Developers should also connect to the MafwExtension::error signal to handle these errors properly.

[edit] Using a renderer

Renderers are extensions, therefore, the first step is to obtain a reference to them using the framework registry. Please check the section on loading extensions for more information on this topic.

Once a reference to a renderer has been obtained, the next step is to assign a playlist to it with mafw_renderer_assign_playlist. And start playing at any moment using mafw_renderer_play. From that moment on, other playback control functions like mafw_renderer_pause or mafw_renderer_stop can also be used.

Usually, it the developer will also want to connect to the renderer signals to capture and report playback errors, enable or disable controls in the UI whenever the renderer playback state or the currently selected media changes.

For further details on the renderer API, please check the MAFW API reference.

[edit] Built-in plugins

MAFW comes with a set of plugins already available. In order to obtain these plugins one has to install the appropriate MAFW packages. Currently, each plugin is delivered in a separate package. Below is a list of these plugins along with a brief description of each one:

  • Tracker Source: A source plugin implementing a MafwSource for accessing multimedia files stored in the local filesystem using Tracker as underlying engine.
  • UPnP Source: A source plugin implementing a MafwSource for accessing multimedia files stored in UPnP servers. This plugin instantiates one source extension per UPnP server discovered. The plugin is implemented using gUPnP.
  • IRadio Source: A source plugin implementing a MafwSource for providing support for multimedia bookmarks.
  • GStreamer Renderer: A renderer plugin implementing a MafwRenderer for playback using GStreamer as playback engine.

[edit] Creating plugins

This sections provides some information for developers interested in creating new extension plugins (Sources and/or Renderers).

[edit] Creating a source

A MAFW source is an element that can provide media information, this means available media resources over a protocol and their metadata. Accessing the media files stored in your local file system or the media served through UPnP servers are some examples.

Creating a MAFW source means creating a shared library containing a GObject that inherits from MafwSource and provide an implementation for some of the methods defined in that class.

All the methods have a default implementation in MafwSource that raise an error by default, so it is not mandatory to implement all the methods, but only the ones you want to support. The most important ones are:

  • mafw_source_browse: it is used to query the source about the available media.
  • mafw_source_cancel_browse: mafw_source_browse operations can take some time, so this method is intended to cancel ongoing browse operations.
  • mafw_source_get_metadata: it is used to request the metadata associated to a given media served by a source. For example, when a renderer tries to play a certain media included in a playlist, it has to obtain the URI associated to that media, this is done by calling mafw_source_get_metadata and passing the object identifier of the media.

Other methods are those related to creation or destruction of media items or metadata modification. Even if your source does not need to implement these methods, it may still be interesting to provide dummy implementations so that other components that use the source do not get errors then using these services. For example, a renderer may use the mafw_source_set_metadata method to automatically increase the play-count of media items when played. If a source returns an error when this method is invoked instead of of providing an empty implementation, the application could end up receiving that error message or the renderer may abort the playback operation, which may or may not be the intended behaviors.

  • mafw_source_set_metadata: Sets metadata for a particular media resource.
  • mafw_source_create_object: Adds a new media resource or container to a particular source.
  • mafw_source_destroy_object: Removes a media item or container from a particular source.

Other than creating a GObject the developer has to define a symbol containing the plugin name and functions to initialize and deinitialize the plugin. An example of this is: Creating a source plugin

# define MAFW_SOURCE_PLUGIN_NAME "MySource-Plugin"
# define MAFW_SOURCE_NAME        "My Source"
# define MAFW_SOURCE_UUID        "mysource"
static gboolean mafw_my_source_initialize(MafwRegistry *registry,
					  GError **error);
static void mafw_my_source_deinitialize(GError **error);
/*
 * Registers the plugin descriptor making this plugin available to the
 * framework and applications
 */
G_MODULE_EXPORT MafwPluginDescriptor mafw_my_source_plugin_description = {
	{ .name = MAFW_MY_SOURCE_PLUGIN_NAME },
	.initialize = mafw_my_source_initialize,
	.deinitialize = mafw_my_source_deinitialize,
};
/*
 * Plugin initialization. Sets UUID and name of this source and then
 * adds it to the registry, making it available to applications.
 */
static gboolean
mafw_my_source_initialize(MafwRegistry *registry,
                          GError **error)
{
	MafwMySource *self = MAFW_MY_SOURCE(mafw_my_source_new());
	mafw_registry_add_extension(registry, MAFW_EXTENSION(self));
	g_object_unref(self);
	return TRUE;
}
/*
 * Plugin deinit
 */
static void
mafw_my_source_deinitialize(GError **error) { }

The code above shows a plugin that can be recognized and loaded. The mafw_my_source_initialize function creates an instance of the source by using the object constructor method, and registers the source in the registry, making it available to applications. The object constructor method

MafwSource *mafw_my_source_new(void)
{
	return MAFW_MY_SOURCE(g_object_new(MAFW_TYPE_MY_SOURCE,
					"plugin", MAFW_MY_SOURCE_PLUGIN_NAME,
					"uuid", MAFW_MY_SOURCE_UUID,
					"name", MAFW_MY_SOURCE_NAME,
					NULL));
}

Notice that the plugin initialization function may instantiate and register several objects (sources in this case). For example, a UPnP plugin may check for available UPnP servers and register a source object per server discovered.

For further details about the parameters received by functions and a deeper description of the MafwSource class, please take a look at the MAFW API reference.

[edit] Creating a renderer

A renderer is an element capable of playing media elements and playlists of media elements.

As in the case of MAFW sources, in order to create a renderer plugin a shared library should be implemented, as well as a module initializer and a GObject inheriting from MafwRenderer. An example would be very similar to the MAFW source's one.

Of course, the methods that have to be redefined are the ones defined at MafwRenderer, and again, the developer may choose not to provide an implementation for some of them. The MafwRenderer class defines many methods that can be classified like this:

  • Playback: Those used to control playback (play, stop, pause, resume, volume, etc)
  • Playlist management: Those used to assign a playlist to the renderer and manipulate it.
  • Status: Used to query the status of the renderer, like its current playback state or selected media.
  • Position: Used to query the current playback position or set it (seeking).

Renderers also define some signals that should be taken into account, such as:

  • MafwRenderer::playlist-changed, which should be emitted when a new playlist is assigned to the renderer.
  • MafwRenderer::media-changed, which should be emitted when new media is selected in the renderer.
  • MafwRenderer::state-changed, which should be emitted when the renderer changes from on state to another.
  • MafwRenderer::buffering-info, which should be emitted to inform about the buffering progress when playing streams.
  • MafwRenderer::metadata-changed, which should be emitted when the renderer obtains metadata information from the currently selected media.

[edit] Other issues

[edit] Properties

MafwExtension objects (renderers and sources) can define properties (MafwExtension properties, not GObject's) to configure them on execution time. To use them, get and set functions have to be provided and hooked in the class_init method: Using extension properties

static void
mafw_my_renderer_get_property(MafwExtension *self,
	 		      const gchar *key,
			      MafwExtensionPropertyCallback callback,
			      gpointer user_data)
{
	MafwMyRenderer *renderer;
	GValue *value = NULL;
	GError *error = NULL;
	g_return_if_fail(MAFW_IS_MY_RENDERER(self));
	g_return_if_fail(callback != NULL);
	g_return_if_fail(key != NULL);
	renderer = MAFW_MY_RENDERER(self);
	if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) {
		value = g_new0(GValue, 1);
		g_value_init(value, G_TYPE_UINT);
		g_value_set_uint(value, volume);
	}
	else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) {
           ...
	}
	else {
		/* Unsupported property */
		error = g_error_new(MAFW_MY_RENDERER_ERROR,
				    MAFW_EXTENSION_ERROR_GET_PROPERTY,
				    "Unsupported property");
	}
	callback(self, key, value, user_data, error);
}
static gboolean
mafw_my_renderer_set_property(MafwExtension *self,
                              const gchar *key,
                              const GValue *value)
{
	MafwMyRenderer *renderer;
	g_return_if_fail(MAFW_IS_MY_RENDERER(self));
	g_return_if_fail(key != NULL);
	renderer = MAFW_MY_RENDERER(self);
	if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) {
		guint volume = g_value_get_uint(value);
		volume = CLAMP(volume, 0, 100);
                _do_set_volume(renderer, volume);
	}
	else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) {
         ...
        }
	else {
		/* Unsupported property */
                return FALSE;
	}
	mafw_extension_emit_property_changed(self, key, value);
        return TRUE;
}
static void mafw_my_renderer_class_init(MafwMyRendererClass *klass)
{
        ...
        MAFW_EXTENSION_CLASS(klass)->get_extension_property =
          (gpointer) mafw_my_renderer_get_property;
        MAFW_EXTENSION_CLASS(klass)->set_extension_property =
          (gpointer) mafw_my_renderer_set_property;
        ...
}

Properties declaration is done in the object's init function: Registering extension properties

static void
mafw_my_renderer_get_property(MafwExtension *self,
	 		      const gchar *key,
			      MafwExtensionPropertyCallback callback,
			      gpointer user_data)
{
	MafwMyRenderer *renderer;
	GValue *value = NULL;
	GError *error = NULL;
	g_return_if_fail(MAFW_IS_MY_RENDERER(self));
	g_return_if_fail(callback != NULL);
	g_return_if_fail(key != NULL);
	renderer = MAFW_MY_RENDERER(self);
	if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) {
		value = g_new0(GValue, 1);
		g_value_init(value, G_TYPE_UINT);
		g_value_set_uint(value, volume);
	}
	else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) {
           ...
	}
	else {
		/* Unsupported property */
		error = g_error_new(MAFW_MY_RENDERER_ERROR,
				    MAFW_EXTENSION_ERROR_GET_PROPERTY,
				    "Unsupported property");
	}
	callback(self, key, value, user_data, error);
}
static gboolean
mafw_my_renderer_set_property(MafwExtension *self,
                              const gchar *key,
                              const GValue *value)
{
	MafwMyRenderer *renderer;
	g_return_if_fail(MAFW_IS_MY_RENDERER(self));
	g_return_if_fail(key != NULL);
	renderer = MAFW_MY_RENDERER(self);
	if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) {
		guint volume = g_value_get_uint(value);
		volume = CLAMP(volume, 0, 100);
                _do_set_volume(renderer, volume);
	}
	else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) {
         ...
        }
	else {
		/* Unsupported property */
                return FALSE;
	}
	mafw_extension_emit_property_changed(self, key, value);
        return TRUE;
}
static void mafw_my_renderer_class_init(MafwMyRendererClass *klass)
{
        ...
        MAFW_EXTENSION_CLASS(klass)->get_extension_property =
          (gpointer) mafw_my_renderer_get_property;
        MAFW_EXTENSION_CLASS(klass)->set_extension_property =
          (gpointer) mafw_my_renderer_set_property;
        ...
}

The code shows examples of renderer properties, but the same could be applied to sources (as this is a mechanism defined for any kind of extension), for example, a slow source could use properties to define an eager vs. lazy strategy or the use of caches.


[edit] Code examples

This section contains a few source code exampled that illustrate how to use MAFW. These examples are referenced from other parts of this tutorial.

[edit] Source browse example

This program is a simple command line program that allows the user to browse the contents of arbitrary containers of the local filesystem source using the mafw-tracker-source plugin. So, be sure you have this plugin installed (and running if you want to use it in out-of-process mode)!


To build the example: Source browse example

libtool --mode=link gcc -o mafw-browse-example \
 `pkg-config --cflags --libs mafw mafw-shared` mafw-browse-example.c

To run the example:

run-standalone.sh ./mafw-browse-example <object-id>

For example, the object identifier of the root container of this source is localtagfs::

run-standalone.sh ./mafw-browse-example localtagfs::

The source code: Source browse example

/* 
The code examples copyrighted by Nokia Corporation that are included to
this material are licensed to you under following MIT-style License:
 
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
 
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
 
#include <string.h>
#include <stdlib.h>
 
#include <glib.h>
 
#include <libmafw/mafw.h>
#include <libmafw/mafw-log.h>
#include <libmafw/mafw-registry.h>
#include <libmafw-shared/mafw-shared.h>
 
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mafw-example"
 
#define WANTED_SOURCE     "Mafw-Tracker-Source"
 
MafwSource *app_source = NULL;
GMainLoop * main_loop = NULL;
 
/*
 * This callback is invoked whenever a browse result is available
 */
static void
browse_request_cb (MafwSource *source,
		   guint browse_id,
		   gint remaining,
		   guint index,
		   const gchar *object_id,
		   GHashTable *metadata,
		   gpointer user_data,
		   const GError *error)
{
	const gchar *title, *artist, *album, *genre;
 
	if (error != NULL) {
		g_error ("Browse error: %s\n", error->message);
	}
 
	if (object_id == NULL) {
		g_print ("[INFO]     Sorry, no songs found!\n");
	} else {
		g_print ("[INFO]     Got result %d:\n", index);
		if (metadata == NULL) {
			title = "Unknown";
			artist = "Unknown";
			album = "Unknown";
			genre = "Unknown";
		} else {
                        GValue *v;
                        v = mafw_metadata_first (metadata,
						 MAFW_METADATA_KEY_TITLE);
                        title = v ? g_value_get_string (v) : "Unknown";
                        v = mafw_metadata_first (metadata,
						 MAFW_METADATA_KEY_ARTIST);
                        artist = v ? g_value_get_string (v) : "Unknown";
                        v = mafw_metadata_first (metadata,
						 MAFW_METADATA_KEY_ALBUM);
                        album = v ? g_value_get_string (v) : "Unknown";
                        v = mafw_metadata_first (metadata,
						 MAFW_METADATA_KEY_GENRE);
                        genre = v ? g_value_get_string (v) : "Unknown";
		}
 
		g_print ("[INFO]       Object ID: %s\n", object_id);
		g_print ("[INFO]           Title: %s\n", title);
		g_print ("[INFO]          Artist: %s\n", artist);
		g_print ("[INFO]           Album: %s\n", album);
		g_print ("[INFO]           Genre: %s\n", genre);
	}
 
        if (remaining == 0) {
		g_print ("[INFO] Browse operaton finished. Exiting...\n");
		g_main_loop_quit (main_loop);
	}
}
 
/*
 * This function executes a browse request on the selected source.
 * This function receives the object identifier to browse as parameter.
 */
static gboolean
do_browse_request (gpointer user_data)
{
	guint browse_id;
	const gchar *const *keys;
	gchar *object_id = (gchar *) user_data;
 
	g_print ("[INFO] Browsing %s on "  WANTED_SOURCE ".\n", object_id);
 
	keys = MAFW_SOURCE_LIST(
	       MAFW_METADATA_KEY_TITLE,
	       MAFW_METADATA_KEY_ARTIST,
	       MAFW_METADATA_KEY_ALBUM,
	       MAFW_METADATA_KEY_GENRE);
 
	browse_id =
		mafw_source_browse (app_source,        /* Source */
				    object_id,         /* Object identifier */
				    FALSE,             /* Recursive */
				    NULL,              /* Filter */
				    NULL,              /* Sorting */
				    keys,              /* Requested keys */
				    0, 30,             /* Offset, Count*/
				    browse_request_cb, /* Callback */
				    NULL);             /* User data */
 
	if (browse_id == MAFW_SOURCE_INVALID_BROWSE_ID) {
		g_warning ("Incorrect browse request.\n");
	}
 
	return FALSE;
}
 
/*
 * Hooks for extension added and removed signals
 */
 
/*
 * Checks for a particular source to be added and 
 * saves a reference to it.
 */
static void
source_added_cb (MafwRegistry *registry,
		 GObject *source,
		 gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		const gchar *name =
			mafw_extension_get_name(MAFW_EXTENSION(source));
 
		g_print("[INFO] Source %s available.\n", name);
 
		if (strcmp (name, WANTED_SOURCE) == 0) {
			g_print ("[INFO]     Wanted source found!\n");
			app_source = g_object_ref (source);
 
			/* When we find the source we are interested in,
			   do a browse request */
			g_timeout_add (1000, do_browse_request, user_data);
		} else {
			g_print ("[INFO]     Not interesting. Skipping...\n");
		}
	}
}
 
/*
 * Checks if the referenced source is removed, and if so, exits.
 */
static void
source_removed_cb (MafwRegistry *registry,
		   GObject *source,
		   gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		g_print("[INFO] Source %s removed.\n",
			mafw_extension_get_name(MAFW_EXTENSION(source)));
 
		if (MAFW_SOURCE (source) == app_source) {
			g_print ("[INFO]     Wanted source removed!"
				 " Exiting...\n");
			g_object_unref (app_source);
			g_main_loop_quit (main_loop);
		}
	}
}
 
static void
renderer_added_cb (MafwRegistry *registry,
		   GObject *renderer,
		   gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		g_print("[INFO] Renderer %s available.\n",
			mafw_extension_get_name(MAFW_EXTENSION(renderer)));
	}
}
 
static void
renderer_removed_cb (MafwRegistry * registry,
		     GObject *renderer,
		     gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		g_print("Renderer %s removed.\n",
			mafw_extension_get_name(MAFW_EXTENSION(renderer)));
	}
}
 
/*
 * Loads MAFW plugins.
 *
 * This function lods out-of-process extensions and hooks to
 * source-added and source-removed signals for dynamic extension
 * discovery and removal.
 *
 * Also, this function allows loading of in-process extensions
 * defined through an environment variable.
 *
 * The object_id parameter is used to browse that object as soon
 * as the source of interest is loaded.
 */
gboolean static
app_init (gchar *object_id)
{
        GError *error = NULL;
	gchar **plugins = NULL;
	GList *extension_list = NULL;
	MafwRegistry *registry = NULL;
 
	/* --- Basic MAFW setup -- */
 
	/* Init GType */
        g_type_init ();
 
	/* Init MAFW log (show all messages) */
	mafw_log_init (G_LOG_DOMAIN ":ALL");
 
	/* -- Start out-of-process plugin loading -- */
 
	g_print ("[INFO] Checking for out-of-process plugins...\n");
 
	/* Check available plugins  */
	registry = MAFW_REGISTRY (mafw_registry_get_instance());
	if (registry == NULL) {
		g_error ("app_init: Failed to get MafwRegistry reference\n");
		return FALSE;
	}
 
	/* Start out-of-process extension discovery */
	mafw_shared_init (registry, &error);
	if (error != NULL)
	{
		g_warning ("Ext. discovery failed: %s",
			   error->message);
		g_error_free(error);
		error = NULL;
	}
 
	/* Connect to extension discovery signals. These signals will be
	   emitted when new extensions are started or removed */
	g_signal_connect (registry,
			  "renderer_added",
			  G_CALLBACK(renderer_added_cb), NULL);
 
	g_signal_connect (registry,
			  "renderer_removed",
			  G_CALLBACK(renderer_removed_cb), NULL);
 
	g_signal_connect (registry,
			  "source_added",
			  G_CALLBACK(source_added_cb), object_id);
 
	g_signal_connect (registry,
			  "source_removed",
			  G_CALLBACK(source_removed_cb), NULL);
 
	/* Also, check for already started extensions */
	extension_list = mafw_registry_get_renderers(registry);
	while (extension_list)
	{
		renderer_added_cb (registry,
				   G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	extension_list = mafw_registry_get_sources(registry);
	while (extension_list)
	{
		source_added_cb (registry,
				 G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	/* -- Start in-process plugin loading -- */
 
	/* MAFW_INP_PLUGINS shold contain a list of paths
	   to plugin files to be loaded in-process */
 
	g_print ("[INFO] Checking for in-process plugins...\n");
	if (g_getenv("MAFW_INP_PLUGINS") != NULL) {
		plugins = g_strsplit (g_getenv ("MAFW_INP_PLUGINS"),
				      G_SEARCHPATH_SEPARATOR_S,
				      0);
 
		for (; NULL != *plugins; plugins++) {
			g_print ("[INFO] Loading in-process plugin %s...\n",
				 *plugins);
 
			mafw_registry_load_plugin (MAFW_REGISTRY(registry),
						   *plugins,
						   &error);
 
			if (error != NULL) {
				gchar* msg;
				msg = g_strdup_printf (
					"Unable to load inp. plugin %s: %s",
					*plugins,
					error->message);
 
				g_warning ("Plugin loading failed: %s", msg);
 
				g_free(msg);
				g_error_free(error);
				error = NULL;
			}
		}
	} else {
		g_print ("[INFO]     No in-process plugins requested.\n");
	}
}
 
int
main (int argc, gchar *argv[])
{
	if (argc != 2) {
		g_error ("Please, provide exactly one argument specifying "
			 "the object identifier of the item to browse.");
	}
 
	g_print ("[INFO] Starting example...\n");
	app_init (argv[1]);
	g_print ("[INFO] Example started.\n");
 
	main_loop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (main_loop);
 
	return 0;
}

[edit] Source metadata example

This program is a simple command line program that allows the user to list some metadata values of arbitrary elements of the local filesystem source using the mafw-tracker-source plugin. So, be sure you have this plugin installed (and running if you want to use it in out-of-process mode)!

To build the example: Source metadata example

libtool --mode=link gcc -o mafw-metadata-example \
 `pkg-config --cflags --libs mafw mafw-shared` mafw-metadata-example.c

To run the example:

run-standalone.sh ./mafw-metadata-example <object-id>

A valid object identifier can be obtained using the browse example, by browsing the all songs container (localtagfs::music/songs), and checking the object identifiers of the contained songs.

The source code: Source metadata example

/*
The code examples copyrighted by Nokia Corporation that are included to
this material are licensed to you under following MIT-style License:
 
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
 
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
 
#include <string.h>
#include <stdlib.h>
 
#include <glib.h>
 
#include <libmafw/mafw.h>
#include <libmafw/mafw-log.h>
#include <libmafw/mafw-registry.h>
#include <libmafw-shared/mafw-shared.h>
 
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mafw-example"
 
#define WANTED_SOURCE     "Mafw-Tracker-Source"
 
MafwSource *app_source = NULL;
GMainLoop * main_loop = NULL;
 
/*
 * This callback is invoked whenever a browse result is available
 */
static void
metadata_request_cb (MafwSource *source,
		     const gchar *object_id,
		     GHashTable *metadata,
		     gpointer user_data,
		     const GError *error)
{
	const gchar *title, *artist, *album, *genre;
 
	if (error != NULL) {
		g_error ("Metadata error: %s\n", error->message);
	}
 
	g_print ("[INFO]     Got metadata:\n");
	if (metadata == NULL) {
		title = "Unknown";
		artist = "Unknown";
		album = "Unknown";
		genre = "Unknown";
	} else {
		GValue *v;
		v = mafw_metadata_first (metadata,
					 MAFW_METADATA_KEY_TITLE);
		title = v ? g_value_get_string (v) : "Unknown";
		v = mafw_metadata_first (metadata,
					 MAFW_METADATA_KEY_ARTIST);
		artist = v ? g_value_get_string (v) : "Unknown";
		v = mafw_metadata_first (metadata,
					 MAFW_METADATA_KEY_ALBUM);
		album = v ? g_value_get_string (v) : "Unknown";
		v = mafw_metadata_first (metadata,
					 MAFW_METADATA_KEY_GENRE);
		genre = v ? g_value_get_string (v) : "Unknown";
	}
 
	g_print ("[INFO]           Title: %s\n", title);
	g_print ("[INFO]          Artist: %s\n", artist);
	g_print ("[INFO]           Album: %s\n", album);
	g_print ("[INFO]           Genre: %s\n", genre);
}
 
/*
 * This function executes a metadata request on the selected source
 * for the object identifier passed as user data.
 */
static gboolean
do_metadata_request (gpointer user_data)
{
	guint browse_id;
	const gchar *const *keys;
	gchar *object_id = (gchar *) user_data;
 
	g_print ("[INFO] Requesting metadata for %s on "
		 WANTED_SOURCE ".\n", object_id);
 
	keys = MAFW_SOURCE_LIST(
	       MAFW_METADATA_KEY_TITLE,
	       MAFW_METADATA_KEY_ARTIST,
	       MAFW_METADATA_KEY_ALBUM,
	       MAFW_METADATA_KEY_GENRE);
 
	mafw_source_get_metadata (app_source,
				  object_id,
				  keys,
				  metadata_request_cb,
				  NULL);
 
	return FALSE;
}
 
/*
 * Hooks for extension added and removed signals
 */
 
/*
 * Checks for a particular source to be added and 
 * saves a reference to it.
 */
static void
source_added_cb (MafwRegistry *registry,
		 GObject *source,
		 gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		const gchar *name =
			mafw_extension_get_name(MAFW_EXTENSION(source));
 
		g_print("[INFO] Source %s available.\n", name);
 
		if (strcmp (name, WANTED_SOURCE) == 0) {
			g_print ("[INFO]     Wanted source found!\n");
			app_source = g_object_ref (source);
 
			/* When we find the source we are interested in,
			   do a metadata request */
			g_timeout_add (1000, do_metadata_request, user_data);
		} else {
			g_print ("[INFO]     Not interesting. Skipping...\n");
		}
	}
}
 
/*
 * Checks if the referenced source is removed, and if so, exits.
 */
static void
source_removed_cb (MafwRegistry *registry,
		   GObject *source,
		   gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		g_print("[INFO] Source %s removed.\n",
			mafw_extension_get_name(MAFW_EXTENSION(source)));
 
		if (MAFW_SOURCE (source) == app_source) {
			g_print ("[INFO]     Wanted source removed!"
				 " Exiting...\n");
			g_object_unref (app_source);
			g_main_loop_quit (main_loop);
		}
	}
}
 
static void
renderer_added_cb (MafwRegistry *registry,
		   GObject *renderer,
		   gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		g_print("[INFO] Renderer %s available.\n",
			mafw_extension_get_name(MAFW_EXTENSION(renderer)));
	}
}
 
static void
renderer_removed_cb (MafwRegistry * registry,
		     GObject *renderer,
		     gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		g_print("Renderer %s removed.\n",
			mafw_extension_get_name(MAFW_EXTENSION(renderer)));
	}
}
 
/*
 * Loads MAFW plugins.
 *
 * This function lods out-of-process extensions and hooks to
 * source-added and source-removed signals for dynamic extension
 * discovery and removal.
 *
 * Also, this function allows loading of in-process extensions
 * defined through an environment variable.
 *
 * The object_id parameter is used to request metadata as soon
 * as the source of interest is loaded.
 */
gboolean static
app_init (gchar *object_id)
{
        GError *error = NULL;
	gchar **plugins = NULL;
	GList *extension_list = NULL;
	MafwRegistry *registry = NULL;
 
	/* --- Basic MAFW setup -- */
 
	/* Init GType */
        g_type_init ();
 
	/* Init MAFW log (show all messages) */
	mafw_log_init (G_LOG_DOMAIN ":ALL");
 
	/* -- Start out-of-process plugin loading -- */
 
	g_print ("[INFO] Checking for out-of-process plugins...\n");
 
	/* Check available plugins  */
	registry = MAFW_REGISTRY (mafw_registry_get_instance());
	if (registry == NULL) {
		g_error ("app_init: Failed to get MafwRegistry reference\n");
		return FALSE;
	}
 
	/* Start out-of-process extension discovery */
	mafw_shared_init (registry, &error);
	if (error != NULL)
	{
		g_warning ("Ext. discovery failed: %s",
			   error->message);
		g_error_free(error);
		error = NULL;
	}
 
	/* Connect to extension discovery signals. These signals will be
	   emitted when new extensions are started or removed */
	g_signal_connect (registry,
			  "renderer_added",
			  G_CALLBACK(renderer_added_cb), NULL);
 
	g_signal_connect (registry,
			  "renderer_removed",
			  G_CALLBACK(renderer_removed_cb), NULL);
 
	g_signal_connect (registry,
			  "source_added",
			  G_CALLBACK(source_added_cb), object_id);
 
	g_signal_connect (registry,
			  "source_removed",
			  G_CALLBACK(source_removed_cb), NULL);
 
	/* Also, check for already started extensions */
	extension_list = mafw_registry_get_renderers(registry);
	while (extension_list)
	{
		renderer_added_cb (registry,
				   G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	extension_list = mafw_registry_get_sources(registry);
	while (extension_list)
	{
		source_added_cb (registry,
				 G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	/* -- Start in-process plugin loading -- */
 
	/* MAFW_INP_PLUGINS shold contain a list of paths
	   to plugin files to be loaded in-process */
 
	g_print ("[INFO] Checking for in-process plugins...\n");
	if (g_getenv("MAFW_INP_PLUGINS") != NULL) {
		plugins = g_strsplit (g_getenv ("MAFW_INP_PLUGINS"),
				      G_SEARCHPATH_SEPARATOR_S,
				      0);
 
		for (; NULL != *plugins; plugins++) {
			g_print ("[INFO] Loading in-process plugin %s...\n",
				 *plugins);
 
			mafw_registry_load_plugin (MAFW_REGISTRY(registry),
						   *plugins,
						   &error);
 
			if (error != NULL) {
				gchar* msg;
				msg = g_strdup_printf (
					"Unable to load inp. plugin %s: %s",
					*plugins,
					error->message);
 
				g_warning ("Plugin loading failed: %s", msg);
 
				g_free(msg);
				g_error_free(error);
				error = NULL;
			}
		}
	} else {
		g_print ("[INFO]     No in-process plugins requested.\n");
	}
}
 
int
main (int argc, gchar *argv[])
{
	if (argc != 2) {
		g_error ("Please, provide exactly one argument specifying "
			 "the object identifier of the item to get "
			 "metadata from.");
	}
 
	g_print ("[INFO] Starting example...\n");
	app_init (argv[1]);
	g_print ("[INFO] Example started.\n");
 
	main_loop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (main_loop);
 
	return 0;
}

[edit] Playlist example

This program is a simple command line program that allows the user to create and remove playlists. It is necessary to have the playlist-daemon running to execute this program.

To build the example: Playlist example

libtool --mode=link gcc -o mafw-playlist-example \
 `pkg-config --cflags --libs mafw mafw-shared` mafw-playlist-example.c

To run the example:

This program accepts several commands: Playlist example

/*
The code examples copyrighted by Nokia Corporation that are included to
this material are licensed to you under following MIT-style License:
 
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
 
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
 
#include <string.h>
#include <stdlib.h>
 
#include <glib.h>
 
#include <libmafw/mafw.h>
#include <libmafw/mafw-log.h>
#include <libmafw-shared/mafw-shared.h>
 
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mafw-example"
 
#define COMMAND_CREATE       1
#define COMMAND_REMOVE       2
#define COMMAND_SHOW         3
#define COMMAND_ADD_ITEM     4
#define COMMAND_REMOVE_ITEM  5
 
GMainLoop *main_loop = NULL;
 
gint command;
gchar *playlist_name;
gchar *object_id;
 
/*
 * This function looks for a particular playlist using its name
 */
static MafwPlaylist *
find_playlist (gchar *name, GError **error)
{
 	MafwPlaylistManager *manager;
	MafwPlaylist *playlist = NULL;
	GPtrArray *list = NULL;
	gint i;
 
	manager = mafw_playlist_manager_get ();
 
	list = mafw_playlist_manager_get_playlists (manager, error);
 
	if (*error != NULL) {
		return NULL;
	}
 
	for (i = 0; i < list->len; i++) {
		playlist = (MafwPlaylist *) g_ptr_array_index (list, i);
		gchar *name = mafw_playlist_get_name (playlist);
		if (strcmp (playlist_name, name) == 0) {
			return playlist;
		}
	}
 
	return NULL;
}
 
/*
 * This function creates a new playlist
 */
static GError *
create_playlist (void)
{
	MafwPlaylistManager *manager;
	MafwProxyPlaylist *playlist;
	GError *error = NULL;
 
	manager = mafw_playlist_manager_get ();
	mafw_playlist_manager_create_playlist (manager,
					       playlist_name,
					       &error);
	return error;
}
 
/*
 * This function removes a playlist
 */
static GError *
remove_playlist (void)
{
	MafwPlaylistManager *manager;
	MafwPlaylist *playlist;
	GError *error = NULL;
	GPtrArray *list = NULL;
	gint i;
 
	manager = mafw_playlist_manager_get ();
 
	playlist = find_playlist (playlist_name, &error);
 
	if (error != NULL)  {
		return error;
	}
 
	if (playlist == NULL) {
		return g_error_new (MAFW_ERROR, 0, "Playlist not found");
	}
 
	mafw_playlist_manager_destroy_playlist (manager,
						playlist,
						&error);
 
	return error;
}
 
/*
 * This function shows the object identifiers contained in a playlist
 */
static GError *
show_playlist (void)
{
	MafwPlaylist *playlist;
	GError *error = NULL;
	gint i;
	guint size;
 
	playlist = find_playlist (playlist_name, &error);
 
	if (error != NULL)  {
		return error;
	}
 
	if (playlist == NULL) {
		return g_error_new (MAFW_ERROR, 0, "Playlist not found");
	}
 
	g_print ("Showing contents for playlist %s...:\n", playlist_name);
	size = mafw_playlist_get_size (playlist, &error);
 
	if (error != NULL) {
		return error;
	}
 
	if (size > 0) {
		for (i = 0; i < size; i++) {
			gchar *id =
				mafw_playlist_get_item (playlist, i, &error);
			if (error != NULL) {
				g_warning ("Error getting item %d "
					   "from playlist: %s\n",
					   i, error->message);
				g_error_free (error);
				error = NULL;
			}
 
			g_print ("  %d %s\n", i, id);
		}
	} else {
		g_print ("Playlist is empty\n");
	}
 
	return error;
}
 
/*
 * This function adds an object identifier to a playlist
 */
static GError *
add_item_to_playlist (void)
{
	MafwPlaylist *playlist;
	GError *error = NULL;
 
	playlist = find_playlist (playlist_name, &error);
 
	if (error != NULL)  {
		return error;
	}
 
	if (playlist == NULL) {
		return g_error_new (MAFW_ERROR, 0, "Playlist not found");
	}
 
	mafw_playlist_insert_item (playlist, 0, object_id, &error);
	return error;
}
 
/*
 * This function removes an item from a playlist
 */
static GError *
remove_item_from_playlist (void)
{
	MafwPlaylist *playlist;
	GError *error = NULL;
	guint size;
	gint i;
 
	playlist = find_playlist (playlist_name, &error);
 
	if (error != NULL)  {
		return error;
	}
 
	if (playlist == NULL) {
		return g_error_new (MAFW_ERROR, 0, "Playlist not found");
	}
 
	g_print ("  Searching for %s in playlist %s\n",
		 object_id, playlist_name);
	size = mafw_playlist_get_size (playlist, &error);
 
	if (error != NULL) {
		return error;
	}
 
	for (i = 0; i < size; i++) {
		gchar *id = mafw_playlist_get_item (playlist, i, &error);
		if (error != NULL) {
			g_warning ("Error getting item %d "
				   "from playlist: %s\n",
				   i, error->message);
			g_error_free (error);
			error = NULL;
		} else if (strcmp (id, object_id) == 0) {
			mafw_playlist_remove_item (playlist, i, &error);
			g_print ("  Element found at position %d\n", i);
			break;
		}
	}
 
	if (i == size) {
		g_print ("  Element not found\n");
	} else {
		g_print ("Playlist %s removed\n", playlist_name);
	}
}
 
/*
 * This function executes the command specified in the command line
 */
static gboolean
execute_command (gpointer user_data)
{
	GError *error;
 
	switch (command) {
	case COMMAND_CREATE:
		error = create_playlist ();
		if (error == NULL) {
			g_print ("Playlist %s created\n", playlist_name);
		}
		break;
	case COMMAND_REMOVE:
		error = remove_playlist ();
		if (error == NULL) {
			g_print ("Playlist %s removed\n", playlist_name);
		}
		break;
	case COMMAND_SHOW:
		error = show_playlist ();
		break;
	case COMMAND_ADD_ITEM:
		error = add_item_to_playlist ();
		if (error == NULL) {
			g_print ("Item %s added to playlist %s\n",
				 object_id, playlist_name);
		}
		break;
	case COMMAND_REMOVE_ITEM:
		error = remove_item_from_playlist ();
		break;
	}
 
	if (error != NULL) {
		g_print ("Operation failed: %s\n", error->message);
	} else {
		g_print ("Operation executed successfully.\n");
	}
 
	g_main_loop_quit (main_loop);
 
	return FALSE;
}
 
/*
 * This function parses the command line and extracts
 * info on the requested command and arguments.
 */
static gboolean
check_command_line (int argc,
		    gchar *argv[],
		    gint *command,
		    gchar **playlist_name,
		    gchar **object_id)
{
	switch (argc) {
	case 3:
		*playlist_name = argv[2];
		if (!strcmp (argv[1], "create")) {
			*command = COMMAND_CREATE;
		} else if (!strcmp (argv[1], "remove")) {
			*command = COMMAND_REMOVE;
		} else if (!strcmp (argv[1], "show")) {
			*command = COMMAND_SHOW;
		} else {
			return FALSE;
		}
		break;
	case 4:
		*playlist_name = argv[2];
		*object_id = argv[3];
		if (!strcmp (argv[1], "add-item")) {
			*command = COMMAND_ADD_ITEM;
		} else if (!strcmp (argv[1], "remove-item")) {
			*command = COMMAND_REMOVE_ITEM;
		} else {
			return FALSE;
		}
		break;
	default:
		return FALSE;
	}
 
	return TRUE;
}
 
int
main (int argc, gchar *argv[])
{
	if (!check_command_line (argc, argv,
				 &command, &playlist_name, &object_id)) {
		g_error ("Please, provide one of these sets of arguments:\n"
			 "  create <playlist-name>\n"
			 "  remove <playlist-name>\n"
			 "  show <playlist-name>\n"
                         "  add-item <playlist-name> <object-id>\n",
			 "  remove-item <playlist-name> <object-id>\n");
	}
 
	g_type_init ();
	mafw_log_init (G_LOG_DOMAIN ":ALL");
 
	g_timeout_add (100, execute_command, NULL);
	main_loop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (main_loop);
 
	return 0;
}

[edit] Renderer example

This program is a simple command line program that allows the user to play arbitrary playlists in a renderer. It is necessary to have the mafw-gst-renderer plugin installed (and running in case you intend to run the program in out-of-process mode) in order to execute the program successfully.

To build the example: Renderer example

libtool --mode=link gcc -o mafw-renderer-example \
 `pkg-config --cflags --libs mafw mafw-shared` mafw-renderer-example.c

To run the example:

run-standalone.sh ./mafw-renderer-example <playlist-name>

One can use the playlist example to define the playlists.

The source code: Renderer example

/*
The code examples copyrighted by Nokia Corporation that are included to
this material are licensed to you under following MIT-style License:
 
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
 
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
 
#include <string.h>
#include <stdlib.h>
 
#include <glib.h>
 
#include <libmafw/mafw.h>
#include <libmafw/mafw-log.h>
#include <libmafw/mafw-registry.h>
#include <libmafw-shared/mafw-shared.h>
 
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mafw-example"
 
#define WANTED_RENDERER     "Mafw-Gst-Renderer"
 
MafwRenderer *app_renderer = NULL;
GMainLoop * main_loop = NULL;
 
gchar *state_str[] = {"STOPPED", "PLAYING", "PAUSED", "TRANSITIONING"};
 
static void
play_cb (MafwRenderer *renderer,
	 gpointer user_data,
	 const GError *error)
{
        if (error != NULL) {
		g_print ("Play operation failed: %s\n", error->message);
	}
}
 
static void
error_cb (MafwRenderer *renderer,
	  GQuark domain,
	  gint code,
	  gchar *message,
	  gpointer user_data)
{
	g_print ("Playback error received: %s\n", message);
}
 
static void
state_changed_cb (MafwRenderer *renderer,
                  MafwPlayState state,
		  gpointer user_data)
{
        g_print("State changed! New state is %s\n",
		state_str[state]);
}
 
static void
media_changed_cb (MafwRenderer *renderer,
		  gint index,
		  gchar *object_id,
		  gpointer user_data)
{
	static gboolean started = FALSE;
 
	g_print ("Media changed: assigned media is %d - %s\n",
		 index, object_id);
 
	/* Start playback right away! */
	if (started == FALSE) {
		started = TRUE;
		mafw_renderer_play (renderer, play_cb, NULL);
	}
}
 
static void
metadata_changed_cb (MafwRenderer *renderer,
		     gchar *key,
		     GValueArray *value,
		     gpointer user_data)
{
	g_print ("  Got metadata %s: %s\n",
		 key,
		 g_strdup_value_contents (&(value->values[0])));
}
 
static MafwPlaylist *
find_playlist (gchar *playlist_name, GError **error)
{
	MafwPlaylistManager *manager;
	MafwPlaylist *playlist = NULL;
	GPtrArray *list = NULL;
	gint i;
 
	manager = mafw_playlist_manager_get ();
 
	list = mafw_playlist_manager_get_playlists (manager, error);
 
	if (*error != NULL) {
		return NULL;
	}
 
	for (i = 0; i < list->len; i++) {
		playlist = (MafwPlaylist *) g_ptr_array_index (list, i);
		gchar *name = mafw_playlist_get_name (playlist);
		if (strcmp (playlist_name, name) == 0) {
			return playlist;
		}
	}
 
	return NULL;
}
 
/*
 * This function assigned a playlist to the renderer.
 * Receives the playlist name to assign as parameter.
 */
static gboolean
do_assign_playlist_request (gpointer user_data)
{
	GError *error = NULL;
	MafwPlaylist *playlist;
	gchar *playlist_name = (gchar *) user_data;
 
	g_print ("[INFO] Assigning playlist %s to "  WANTED_RENDERER ".\n",
		 playlist_name);
 
	playlist = find_playlist (playlist_name, &error);
 
	if (error != NULL)  {
		g_error ("Failed to find playlist\n");
	}
 
	if (playlist == NULL) {
		g_error ("Playlist not found");
	}
 
	mafw_renderer_assign_playlist (app_renderer, playlist, &error);
 
	if (error != NULL) {
		g_error ("Failed to assign playlist: %s\n",
			 error->message);
	}
 
	return FALSE;
}
 
/*
 * Hooks for extension added and removed signals
 */
 
/*
 * Checks for a particular source to be added and 
 * saves a reference to it.
 */
static void
source_added_cb (MafwRegistry *registry,
		 GObject *source,
		 gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		const gchar *name =
			mafw_extension_get_name(MAFW_EXTENSION(source));
		g_print("[INFO] Source %s available.\n", name);
	}
}
 
/*
 * Checks if the referenced source is removed, and if so, exits.
 */
static void
source_removed_cb (MafwRegistry *registry,
		   GObject *source,
		   gpointer user_data)
{
	if (MAFW_IS_SOURCE(source)) {
		g_print("[INFO] Source %s removed.\n",
			mafw_extension_get_name(MAFW_EXTENSION(source)));
	}
}
 
static void
renderer_added_cb (MafwRegistry *registry,
		   GObject *renderer,
		   gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		const gchar *name =
			mafw_extension_get_name (MAFW_EXTENSION(renderer));
 
		g_print("[INFO] Renderer %s available.\n", name);
 
		if (strcmp (name, WANTED_RENDERER) == 0) {
			g_print ("[INFO]     Wanted renderer found!\n");
			app_renderer = g_object_ref (renderer);
 
			/* Connect to a few interesting signals */
			g_signal_connect (renderer,
					  "media-changed",
					  G_CALLBACK (media_changed_cb),
					  NULL);
			g_signal_connect (renderer,
					  "state-changed",
					  G_CALLBACK (state_changed_cb),
					  NULL);
			g_signal_connect (renderer,
					  "metadata-changed",
					  G_CALLBACK (metadata_changed_cb),
					  NULL);
			g_signal_connect (renderer,
					  "error",
					  G_CALLBACK (error_cb),
					  NULL);
 
			/* When we find the renderer we are interested in,
			   so use it to play something */
			g_timeout_add (1000,
				       do_assign_playlist_request,
				       user_data);
		} else {
			g_print ("[INFO]     Not interesting. Skipping...\n");
		}
	}
}
 
static void
renderer_removed_cb (MafwRegistry * registry,
		     GObject *renderer,
		     gpointer user_data)
{
	if (MAFW_IS_RENDERER(renderer)) {
		const gchar *name =
			mafw_extension_get_name (MAFW_EXTENSION(renderer));
 
		g_print("[INFO] Renderer %s removed.\n", name);
 
		if (MAFW_RENDERER (renderer) == app_renderer) {
			g_print ("[INFO]     Wanted renderer removed!"
				 " Exiting...\n");
			g_object_unref (app_renderer);
			g_main_loop_quit (main_loop);
		}
	}
}
 
/*
 * Loads MAFW plugins.
 *
 * This function lods out-of-process extensions and hooks to
 * renderer/source-added and renderer/source-removed signals 
 * for dynamic extension discovery and removal.
 *
 * Also, this function allows loading of in-process extensions
 * defined through an environment variable.
 *
 * The object_id parameter is used to play that object as soon
 * as the renderer of interest is loaded.
 */
gboolean static
app_init (gchar *playlist_name)
{
        GError *error = NULL;
	gchar **plugins = NULL;
	GList *extension_list = NULL;
	MafwRegistry *registry = NULL;
 
	/* --- Basic MAFW setup -- */
 
	/* Init GType */
        g_type_init ();
 
	/* Init MAFW log (show all messages) */
	mafw_log_init (G_LOG_DOMAIN ":ALL");
 
	/* -- Start out-of-process plugin loading -- */
 
	g_print ("[INFO] Checking for out-of-process plugins...\n");
 
	/* Check available plugins  */
	registry = MAFW_REGISTRY (mafw_registry_get_instance());
	if (registry == NULL) {
		g_error ("app_init: Failed to get MafwRegistry reference\n");
		return FALSE;
	}
 
	/* Start out-of-process extension discovery */
	mafw_shared_init (registry, &error);
	if (error != NULL)
	{
		g_warning ("Ext. discovery failed: %s",
			   error->message);
		g_error_free(error);
		error = NULL;
	}
 
	/* Connect to extension discovery signals. These signals will be
	   emitted when new extensions are started or removed */
	g_signal_connect (registry,
			  "renderer_added",
			  G_CALLBACK(renderer_added_cb), playlist_name);
 
	g_signal_connect (registry,
			  "renderer_removed",
			  G_CALLBACK(renderer_removed_cb), NULL);
 
	g_signal_connect (registry,
			  "source_added",
			  G_CALLBACK(source_added_cb), NULL);
 
	g_signal_connect (registry,
			  "source_removed",
			  G_CALLBACK(source_removed_cb), NULL);
 
	/* Also, check for already started extensions */
	extension_list = mafw_registry_get_renderers(registry);
	while (extension_list)
	{
		renderer_added_cb (registry,
				   G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	extension_list = mafw_registry_get_sources(registry);
	while (extension_list)
	{
		source_added_cb (registry,
				 G_OBJECT(extension_list->data), NULL);
		extension_list = g_list_next(extension_list);
	}
 
	/* -- Start in-process plugin loading -- */
 
	/* MAFW_INP_PLUGINS shold contain a list of paths
	   to plugin files to be loaded in-process */
 
	g_print ("[INFO] Checking for in-process plugins...\n");
	if (g_getenv("MAFW_INP_PLUGINS") != NULL) {
		plugins = g_strsplit (g_getenv ("MAFW_INP_PLUGINS"),
				      G_SEARCHPATH_SEPARATOR_S,
				      0);
 
		for (; NULL != *plugins; plugins++) {
			g_print ("[INFO] Loading in-process plugin %s...\n",
				 *plugins);
 
			mafw_registry_load_plugin (MAFW_REGISTRY(registry),
						   *plugins,
						   &error);
 
			if (error != NULL) {
				gchar* msg;
				msg = g_strdup_printf (
					"Unable to load inp. plugin %s: %s",
					*plugins,
					error->message);
 
				g_warning ("Plugin loading failed: %s", msg);
 
				g_free(msg);
				g_error_free(error);
				error = NULL;
			}
		}
	} else {
		g_print ("[INFO]     No in-process plugins requested.\n");
	}
}
 
int
main (int argc, gchar *argv[])
{
	if (argc != 2) {
		g_error ("Please, provide exactly one argument specifying "
			 "the name of the playlist to assign and play.");
	}
 
	g_print ("[INFO] Starting example...\n");
	app_init (argv[1]);
	g_print ("[INFO] Example started.\n");
 
	main_loop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (main_loop);
 
	return 0;
}