Haiku BMediaAddOn: Media Node Factories and Flavor Discovery
Build Haiku media add-ons that describe node flavors, instantiate nodes safely, expose configuration, and handle discovery and lifecycle failures.
BMediaAddOn is the Media Kit factory interface for creating BMediaNode instances. An add-on describes the kinds of nodes it can provide, publishes the formats they accept or produce, and instantiates a concrete node when requested. This discovery layer lets applications and the media roster reason about capabilities before creating an active node.
An add-on is not simply a shared library containing a class. It crosses a system boundary: the media infrastructure must load or otherwise interact with it, inspect flavor metadata, manage node instantiation, and cope with failure. A production add-on therefore needs stable flavor descriptions, truthful capability declarations, clear configuration serialization, and teardown that does not strand a media thread or device handle.
Flavors are capability declarations
The public flavor_info structure includes a name, description, node-kind flags, flavor flags, an internal identifier, possible instance count, and input/output format descriptions. CountFlavors() reports how many flavors the add-on exposes, and GetFlavorAt() returns the corresponding metadata. These descriptions are used for discovery and should be accurate enough for a client or media service to select a compatible node.
Do not advertise a producer output format that the node cannot actually generate or a consumer input format that it cannot safely accept. A wildcard declaration can help express negotiation flexibility, but broad wildcards can attract connections that fail later. Keep the declaration conservative and use connection-time negotiation to specialize compatible formats.
possible_count and flavor flags influence how nodes may be instantiated or made available. The header documents global and local flavor flags, including behavior in relation to the media add-on server and loading application. Treat these as architectural controls, not UI labels. Verify the exact semantics in the current API documentation and runtime before relying on a process-placement assumption.
Give each flavor a stable internal ID within the add-on. Do not use the array index as a persistent identity if flavors can be reordered between releases. Configuration should identify the flavor and version its own serialized fields so old settings can be migrated or rejected with an understandable message.
Implement the factory contract
InitCheck() reports whether the add-on can operate and can provide failure text. InstantiateNodeFor() receives a flavor_info, configuration message, and status output, then returns a node or failure. The factory should validate the requested flavor ID and configuration before opening expensive resources. On failure, it must return a clear status and avoid leaking partially initialized hardware, threads, file descriptors, or buffers.
The returned object is a BMediaNode, not a pointer that arbitrary applications should dereference across team boundaries. The Media Kit uses node identifiers and protocol calls for control. Keep implementation state inside the node and expose only documented controls and configuration. Never store an application’s stack pointer from the factory request for later callback use.
The media add-on may be asked for configuration through GetConfigurationFor(). Persist only stable, necessary settings: selected device identifier, format choice, or user preference. Do not serialize transient pointers, semaphores, team-local area IDs, or values that are meaningful only for one runtime session. Validate configuration messages as untrusted input: check field presence, types, lengths, enum ranges, and version before use.
If flavor capabilities change dynamically, the API provides NotifyFlavorChange() to notify listeners and cause a rescan. It is documented as thread-safe. Call it only after the internal state that backs the flavor description is consistent. Excessive notifications can create churn; batch related changes and report a device add/remove transition once the new inventory is ready.
File sniffing is a narrow responsibility
BMediaAddOn includes hooks such as SniffRef(), SniffType(), and SniffTypeKind() for file-interface nodes. The header explicitly limits SniffRef() to add-ons with a file-interface node. It also warns that SniffType() is broken for nodes that are both producers and consumers and advises using SniffTypeKind() in that case. Follow these header caveats; do not use a sniff hook as a general-purpose device discovery callback.
Sniffing should be bounded and non-destructive. Inspect enough metadata to decide whether the add-on can handle a reference, but do not consume or rewrite input during discovery. Return a quality value only according to the API’s expectations and avoid claiming that a weak signature match proves a file is valid. The actual node must parse the content defensively when it opens or decodes it.
Keep add-on, node, and server lifetimes distinct
The factory object and the node instances it creates have different lifetimes. An add-on can create multiple node instances depending on flavor limits and flags. Do not keep one mutable global buffer or connection state shared across instances unless that sharing is deliberate and synchronized. Each node must clean up its own graph connections, workers, groups, and device state.
The header exposes the underlying image_id and add-on ID through accessors. These identify runtime resources, not permanent identities suitable for a settings file. If the add-on unloads or the media service restarts, those values can change. Persist the flavor and configuration semantics, not runtime IDs.
Avoid work in static constructors. Add-on load and unload can occur in a context where expensive initialization blocks discovery or makes failure hard to report. Use InitCheck() for bounded validation, and defer node-specific device acquisition until instantiation if that is the appropriate resource boundary. Ensure cleanup is safe even when initialization stopped halfway through.
Example factory outline
The exported creation function is declared when building a media add-on. A simplified shape is:
extern "C" BMediaAddOn*
make_media_addon(image_id image)
{
return new ExampleMediaAddOn(image);
}
The declaration and export requirements are build-configuration-sensitive; include the public MediaAddOn.h contract and follow a current Haiku add-on example for project settings. The factory constructor should not claim success before validating its state. The returned add-on’s CountFlavors(), GetFlavorAt(), and InstantiateNodeFor() must agree on flavor count, IDs, capabilities, and creation behavior.
When copying a flavor_info, pay attention to pointer members such as format arrays. The returned data must remain valid for the duration expected by the API. Do not return pointers to temporary stack arrays. Prefer stable member storage or immutable static descriptors when appropriate, and ensure no mutable shared array is modified while the media service is reading it.
Failure behavior and observability
Discovery can fail before a node is instantiated: image load, add-on initialization, malformed flavor metadata, missing hardware, unavailable format, or configuration mismatch. Return the narrowest meaningful status, populate failure text where supported, and log the add-on and flavor identifiers. Avoid a generic “not found” for every failure; it makes users and developers unable to distinguish unsupported hardware from invalid configuration.
Instantiation can fail after partial resource acquisition. Structure setup as reversible steps: validate input, acquire device, initialize node state, register internal resources, and publish success. On any failure, unwind completed steps in reverse order. Do not start an untracked thread before the node is fully capable of stopping it.
Make diagnostics identify add-on version/revision, flavor name and ID, device, negotiated format, and failing stage. Rate-limit repeated errors from hot-plug loops. A rescan that repeatedly rediscovers an unavailable device should not flood logs or spin CPU.
Testing and acceptance criteria
Test InitCheck() success and failure, zero flavors if that is a valid design, a single flavor, every flavor index, invalid index, bad config fields, unsupported formats, missing devices, and repeated instantiation/destruction. Confirm flavor format arrays remain valid during discovery. Test multiple instances if supported, including concurrent nodes that use the same hardware.
Simulate add-on load failure, node construction failure after resource acquisition, device removal while active, and media service restart. Verify the node disconnects cleanly and the factory does not retain dead pointers. Test NotifyFlavorChange() after a real capability change and confirm the roster sees an accurate refreshed list.
Acceptance means every published flavor can be instantiated under its declared conditions, all failure paths release resources, configuration survives a restart without runtime IDs, and capability updates remain truthful. If an add-on only works on one tested machine, document that limit rather than overstating the flavor’s compatibility.
BMediaAddOn provides a clean seam between discovery and active media nodes. Keeping flavor metadata truthful and treating factory requests as fallible creates a graph that the rest of Haiku can inspect without guessing what the plug-in will do.
Related:
- How to Design a Haiku Translation Kit Add-On Without Corrupting Input Data
- Haiku’s Translation Kit: Roster-Based Media Conversion Between Applications
Sources: