Native Dart and Flutter bindings for BlueZ media APIs on Linux, backed by
dart:ffi and sdbus-c++.
This package currently focuses on the tested BlueZ media receiver/control surface: remote media player control, controller commands, and audio transport inspection/acquisition.
- Control remote
org.bluez.MediaPlayer1players: play, pause, stop, next, previous, repeat, shuffle, and property snapshots - Use
org.bluez.MediaControl1controller commands and connectivity snapshots - Inspect and control
org.bluez.MediaTransport1properties, volume, and file descriptor acquisition - Receive native ObjectManager updates through a Dart
ReceivePort - Bundle the native library in Flutter Linux apps through the FFI plugin build
Planned for a future release:
- Register a local
org.bluez.MediaPlayer1object throughorg.bluez.Media1 - Browse
org.bluez.MediaFolder1andorg.bluez.MediaItem1trees
org.bluez.MediaEndpoint1 XML and generated proxy definitions are included for
code generation, but endpoint registration is not currently exposed by the
public Dart API.
| Platform | MediaPlayer1 | MediaControl1 | MediaTransport1 | Media1 / Browsing |
|---|---|---|---|---|
| Linux with BlueZ | Tested | Tested | Tested | Planned |
| macOS | No | No | No | No |
| Windows | No | No | No | No |
Ubuntu/Debian:
sudo apt-get install cmake ninja-build clang libsystemd-dev pkg-configFedora:
sudo dnf install cmake ninja-build clang systemd-devel pkgconf-pkg-configdependencies:
bluez_media_native: ^0.0.1Flutter Linux apps build and bundle the native library automatically through the FFI plugin configuration. For plain Dart examples, build it manually:
cmake -S native -B build/native -GNinja -DCMAKE_BUILD_TYPE=Release
cmake --build build/native --parallelIf the library is not available through the system loader, point the Dart code at the built shared object:
export BLUEZ_MEDIA_LIB="$PWD/build/native/libbluez_media_native.so"dart run example/player_control.dart list
dart run example/media_control.dart list
dart run example/transport_control.dart listcd example/flutter_ble_audio
flutter run -d linuxThe Flutter example discovers BlueZ media objects and groups them by Bluetooth device. It can refresh snapshots, send playback commands, adjust volume, set repeat/shuffle modes, and inspect transport metadata.
import 'package:bluez_media_native/bluez_media_native.dart';
Future<void> main() async {
final client = BluezMediaClient.create();
try {
await client.ready;
for (final player in client.players) {
print('${player.name}: ${player.status}');
print(player.track.map((p) => '${p.key}=${p.value}').join(', '));
}
if (client.players.isNotEmpty) {
final player = client.players.first;
player.play();
player.refresh();
}
} finally {
client.close();
}
}Top-level entry point. BluezMediaClient.create() initializes the Dart Native
DL API, opens a BlueZ system bus connection, starts the native event loop, and
caches media objects from BlueZ ObjectManager updates.
| Property / Method | Description |
|---|---|
ready |
Completes after the initial ObjectManager snapshot is cached |
players |
Cached MediaPlayer1 proxies |
controls |
Cached MediaControl1 proxies |
folders |
Cached MediaFolder1 proxies, planned for future tested support |
items |
Cached MediaItem1 proxies, planned for future tested support |
transports |
Cached MediaTransport1 proxies |
transportAdded / transportRemoved |
Streams for transport lifecycle events |
registerPlayer() / unregisterPlayer() |
Register or remove a local media player, planned for future tested support |
getManagedObjects() |
Return a snapshot of known BlueZ media objects |
close() |
Stop native event processing and release cached proxies |
org.bluez.Media1 is exposed on adapter paths such as /org/bluez/hci0.
Use it only when this Linux process wants to publish a local media player to
BlueZ. You do not need this to control a phone's remote
/org/bluez/hci0/dev_.../avrcp/player0 object.
This API is planned for a future tested release.
import 'package:bluez_media_native/bluez_media_native.dart';
Future<void> main() async {
final client = BluezMediaClient.create();
try {
client.registerPlayer(
const BluezMediaPlayerRegistrationConfig(
adapterPath: '/org/bluez/hci0',
playerPath: '/bluez_media/player0',
name: 'bluez_media_native',
type: 'audio',
browsable: true,
searchable: true,
),
);
// Keep the process alive while the player is registered.
await Future<void>.delayed(const Duration(seconds: 30));
client.unregisterPlayer(
adapterPath: '/org/bluez/hci0',
playerPath: '/bluez_media/player0',
);
} finally {
client.close();
}
}CLI equivalent:
dart run example/register_player.dart list
dart run example/register_player.dart \
--adapter /org/bluez/hci0 \
--player /bluez_media/player0 \
--name bluez_media_native \
--hold 30Proxy for a remote org.bluez.MediaPlayer1 object.
| Method / Property | Description |
|---|---|
play(), pause(), stop() |
Playback control |
next(), previous() |
Track navigation |
setRepeat(), setShuffle() |
Set BlueZ player modes when supported |
refresh() |
Fetch the latest property snapshot |
status, position, track, name |
Current player metadata |
propertiesChanged |
Stream of changed property names after updates |
Proxy for org.bluez.MediaControl1.
| Method / Property | Description |
|---|---|
play(), pause(), stop() |
Controller playback commands |
next(), previous() |
Controller track navigation |
volumeUp(), volumeDown() |
Controller volume commands |
fastForward(), rewind() |
Controller seek commands |
connected, playerPath |
Current control snapshot |
AVRCP browsing helpers for org.bluez.MediaFolder1 and
org.bluez.MediaItem1. These APIs are planned for a future tested release.
| Method / Property | Description |
|---|---|
folder.listItems() |
List child folders/items |
folder.search(value) |
Search under a folder |
folder.changeFolderPath(path) |
Change the current browsing folder |
item.play() |
Play a media item |
item.addToNowPlaying() |
Add an item to the now-playing list |
item.metadata, item.playable |
Item metadata snapshot |
Proxy for org.bluez.MediaTransport1.
| Method / Property | Description |
|---|---|
refresh() |
Fetch the latest transport properties |
volume |
Get or set absolute transport volume |
acquire() / tryAcquire() |
Acquire a duplicated transport file descriptor |
release() |
Release the BlueZ transport |
uuid, codec, state, configuration |
Transport metadata |
BluezMediaAcquiredTransport.close() |
Close the duplicated file descriptor |
The C ABI uses package-owned status codes, not D-Bus numeric error codes:
| Code | Name | Meaning |
|---|---|---|
0 |
BLUEZ_MEDIA_SUCCESS |
Operation succeeded |
-1 |
BLUEZ_MEDIA_ERROR_INVALID_ARGUMENT |
Null handle/path, invalid capacity, or invalid input |
-2 |
BLUEZ_MEDIA_ERROR_BUFFER_TOO_SMALL |
Caller-provided output buffer is too small |
-3 |
BLUEZ_MEDIA_ERROR_OPERATION_FAILED |
D-Bus or native operation failed |
-4 |
BLUEZ_MEDIA_ERROR_UNSUPPORTED_SETTING |
BlueZ rejected repeat/shuffle setting |
-5 |
BLUEZ_MEDIA_ERROR_ALREADY_EXISTS |
Local player path is already registered |
-6 |
BLUEZ_MEDIA_ERROR_NOT_FOUND |
Local player path is not registered |
example/player_control.dart- list and control remoteMediaPlayer1objectsexample/media_control.dart- useMediaControl1commands and snapshotsexample/transport_control.dart- inspect, acquire, release, and set volume on media transportsexample/flutter_ble_audio/- Flutter Linux media-control UI
Future support examples:
example/register_player.dart- register this process as a local BlueZ media playerexample/media_browsing.dart- browse folders, search, inspect items, and play media items
More command examples are available in example/README.md.
native/- C/C++ source, CMake configuration, generated sdbus-c++ proxies, and BlueZ D-Bus XML interfaceslib/- Dart API and FFI codec/bindingslinux/- Flutter Linux plugin build integrationexample/- CLI examples and the Flutter Linux demo apptest/andnative/test/- Dart codec tests and native wire-type tests
cmake -S native -B build/native -GNinja -DBUILD_TESTING=ON
cmake --build build/native --parallel
ctest --test-dir build/native --output-on-failureRun Dart tests:
flutter testRegenerate Dart FFI bindings from native/include/bluez_media_native.h:
dart run ffigen --config ffigen.yamlRegenerate sdbus-c++ proxy headers from the BlueZ XML files:
./scripts/generate_proxies.shThe checked-in files under native/generated/ are generated protocol artifacts.
Runtime bridge classes currently use the hand-written wrappers under
native/include/ and native/src/.
systemctl status bluetooth
sudo systemctl start bluetoothMedia objects appear only when BlueZ exposes them for connected devices. Pair and connect an audio-capable device, then check:
bluetoothctl show
bluetoothctl devices Connected
busctl tree org.bluezThe adapter may be powered off or blocked:
bluetoothctl power on
rfkill list bluetooth
sudo rfkill unblock bluetoothSome BlueZ operations require local system bus permissions. During development, try running the CLI example with elevated privileges while preserving the native library path:
sudo BLUEZ_MEDIA_LIB="$PWD/build/native/libbluez_media_native.so" \
dart run example/player_control.dart listFor general Flutter plugin and Linux desktop help, see the Flutter documentation.