diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c
index 3816763f3a8c9d768619049fce77010e27e859d8..0c6cce7cfc30297473f0b622e281778df858a9d6 100644
--- a/lib/wp/endpoint.c
+++ b/lib/wp/endpoint.c
@@ -93,6 +93,7 @@ struct _WpEndpointPrivate
 {
   gchar *name;
   gchar media_class[40];
+  guint direction;
   GPtrArray *streams;
   GPtrArray *controls;
   GPtrArray *links;
@@ -104,6 +105,7 @@ enum {
   PROP_CORE,
   PROP_NAME,
   PROP_MEDIA_CLASS,
+  PROP_DIRECTION,
 };
 
 enum {
@@ -211,6 +213,9 @@ wp_endpoint_set_property (GObject * object, guint property_id,
     strncpy (priv->media_class, g_value_get_string (value),
         sizeof (priv->media_class) - 1);
     break;
+  case PROP_DIRECTION:
+    priv->direction = g_value_get_uint(value);
+    break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -234,6 +239,9 @@ wp_endpoint_get_property (GObject * object, guint property_id, GValue * value,
   case PROP_MEDIA_CLASS:
     g_value_set_string (value, priv->media_class);
     break;
+  case PROP_DIRECTION:
+    g_value_set_uint (value, priv->direction);
+    break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -281,6 +289,15 @@ wp_endpoint_class_init (WpEndpointClass * klass)
   signals[SIGNAL_NOTIFY_CONTROL_VALUE] = g_signal_new ("notify-control-value",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
       G_TYPE_NONE, 1, G_TYPE_UINT);
+
+  /**
+   * WpEndpoint::direction:
+   * The direction of the endpoint: input = 0, output = 1.
+   */
+  g_object_class_install_property (object_class, PROP_DIRECTION,
+      g_param_spec_uint ("direction", "direction",
+          "The direction of the endpoint", 0, 1, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 }
 
 /**
@@ -465,6 +482,17 @@ wp_endpoint_get_media_class (WpEndpoint * self)
   return priv->media_class;
 }
 
+guint
+wp_endpoint_get_direction (WpEndpoint * self)
+{
+  WpEndpointPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_ENDPOINT (self), -1);
+
+  priv = wp_endpoint_get_instance_private (self);
+  return priv->direction;
+}
+
 /**
  * wp_endpoint_register_stream:
  * @self: the endpoint
diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h
index 2ef688b65ffc55e1c5b7163f8cc97eb519b62c63..b41cc33f72f08afeda3bd0302122c152ab80ff7b 100644
--- a/lib/wp/endpoint.h
+++ b/lib/wp/endpoint.h
@@ -48,6 +48,7 @@ GPtrArray * wp_endpoint_find (WpCore * core, const gchar * media_class_lookup);
 WpCore *wp_endpoint_get_core (WpEndpoint * self);
 const gchar * wp_endpoint_get_name (WpEndpoint * self);
 const gchar * wp_endpoint_get_media_class (WpEndpoint * self);
+guint wp_endpoint_get_direction (WpEndpoint * self);
 
 void wp_endpoint_register_stream (WpEndpoint * self, GVariant * stream);
 GVariant * wp_endpoint_get_stream (WpEndpoint * self, guint32 stream_id);
diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c
index e3e825765eddba35031edde2182f1b7bbe17a592..c50a512319ac1cc13795c4ae0cf5d68f9b2118bd 100644
--- a/modules/module-pipewire/simple-endpoint.c
+++ b/modules/module-pipewire/simple-endpoint.c
@@ -42,9 +42,6 @@ struct _WpPipewireSimpleEndpoint
   /* Handler */
   gulong proxy_node_done_handler_id;
 
-  /* Direction */
-  enum pw_direction direction;
-
   /* Proxies */
   WpProxyNode *proxy_node;
   struct spa_hook node_proxy_listener;
@@ -230,6 +227,7 @@ on_port_added(WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
 static void
 emit_endpoint_ports(WpPipewireSimpleEndpoint *self)
 {
+  enum pw_direction direction = wp_endpoint_get_direction (WP_ENDPOINT (self));
   struct pw_node_proxy* node_proxy = NULL;
   struct spa_audio_info_raw format = { 0, };
   struct spa_pod *param;
@@ -252,7 +250,7 @@ emit_endpoint_ports(WpPipewireSimpleEndpoint *self)
   param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
   param = spa_pod_builder_add_object(&pod_builder,
       SPA_TYPE_OBJECT_ParamPortConfig,  SPA_PARAM_PortConfig,
-      SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(self->direction),
+      SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(direction),
       SPA_PARAM_PORT_CONFIG_mode,       SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
       SPA_PARAM_PORT_CONFIG_format,     SPA_POD_Pod(param));
 
@@ -323,7 +321,6 @@ wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority,
 {
   WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (initable);
   g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
-  const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self));
   struct pw_node_proxy *node_proxy = NULL;
 
   /* Create the async task */
@@ -332,14 +329,6 @@ wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority,
   /* Init the proxies_port array */
   self->proxies_port = g_ptr_array_new_full(2, (GDestroyNotify)g_object_unref);
 
-  /* Set the direction */
-  if (g_str_has_prefix (media_class, "Stream/Input"))
-    self->direction = PW_DIRECTION_INPUT;
-  else if (g_str_has_prefix (media_class, "Stream/Output"))
-    self->direction = PW_DIRECTION_OUTPUT;
-  else
-    g_critical ("failed to parse direction");
-
   /* Register a port_added callback */
   self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
   g_return_if_fail(self->remote_pipewire);
@@ -579,6 +568,7 @@ simple_endpoint_factory (WpFactory * factory, GType type,
 {
   g_autoptr (WpCore) core = NULL;
   const gchar *name, *media_class;
+  guint direction;
   guint global_id;
 
   /* Make sure the type is correct */
@@ -593,6 +583,8 @@ simple_endpoint_factory (WpFactory * factory, GType type,
       return;
   if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
       return;
+  if (!g_variant_lookup (properties, "direction", "u", &direction))
+      return;
   if (!g_variant_lookup (properties, "global-id", "u", &global_id))
       return;
 
@@ -601,6 +593,7 @@ simple_endpoint_factory (WpFactory * factory, GType type,
       "core", core,
       "name", name,
       "media-class", media_class,
+      "direction", direction,
       "global-id", global_id,
       NULL);
 }
diff --git a/modules/module-pw-alsa-udev.c b/modules/module-pw-alsa-udev.c
index af474eae4d167c4f37e3cd68a1fcd5e4421959e2..48cd8d25d217ffd9fa46bff549805cfc9bd85b2c 100644
--- a/modules/module-pw-alsa-udev.c
+++ b/modules/module-pw-alsa-udev.c
@@ -96,6 +96,7 @@ on_node_added(WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
   const struct spa_dict *props = p;
   g_autoptr (WpCore) core = wp_module_get_core (impl->module);
   const gchar *media_class, *name;
+  enum pw_direction direction;
   GVariantBuilder b;
   g_autoptr (GVariant) endpoint_props = NULL;
 
@@ -116,12 +117,28 @@ on_node_added(WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
   if (!name)
     name = spa_dict_lookup (props, "node.name");
 
+  /* Don't handle bluetooth nodes */
+  if (g_str_has_prefix (name, "api.bluez5"))
+    return;
+
+  /* Get the direction */
+  if (g_str_has_prefix (media_class, "Audio/Sink")) {
+    direction = PW_DIRECTION_INPUT;
+  } else if (g_str_has_prefix (media_class, "Audio/Source")) {
+    direction = PW_DIRECTION_OUTPUT;
+  } else {
+    g_critical ("failed to parse direction");
+    return;
+  }
+
   /* Set the properties */
   g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
   g_variant_builder_add (&b, "{sv}",
       "name", g_variant_new_string (name));
   g_variant_builder_add (&b, "{sv}",
       "media-class", g_variant_new_string (media_class));
+  g_variant_builder_add (&b, "{sv}",
+      "direction", g_variant_new_uint32 (direction));
   g_variant_builder_add (&b, "{sv}",
       "global-id", g_variant_new_uint32 (id));
   g_variant_builder_add (&b, "{sv}",
diff --git a/modules/module-pw-audio-client.c b/modules/module-pw-audio-client.c
index a0dd61fe415d9cf378414f756d9df741e7f5e944..379fe04adddf46b4cc77ee3c44099fea7007ac38 100644
--- a/modules/module-pw-audio-client.c
+++ b/modules/module-pw-audio-client.c
@@ -57,6 +57,7 @@ on_node_added (WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
   const struct spa_dict *props = p;
   g_autoptr (WpCore) core = wp_module_get_core (data->module);
   const gchar *name, *media_class;
+  enum pw_direction direction;
   GVariantBuilder b;
   g_autoptr (GVariant) endpoint_props = NULL;
 
@@ -70,6 +71,16 @@ on_node_added (WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
   if (!g_str_has_prefix (media_class, "Stream/"))
     return;
 
+  /* Get the direction */
+  if (g_str_has_prefix (media_class, "Stream/Input")) {
+    direction = PW_DIRECTION_INPUT;
+  } else if (g_str_has_prefix (media_class, "Stream/Output")) {
+    direction = PW_DIRECTION_OUTPUT;
+  } else {
+    g_critical ("failed to parse direction");
+    return;
+  }
+
   /* Get the name */
   name = spa_dict_lookup (props, "media.name");
   if (!name)
@@ -83,6 +94,8 @@ on_node_added (WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
       g_variant_new_take_string (g_strdup_printf ("Stream %u", id)));
   g_variant_builder_add (&b, "{sv}",
       "media-class", g_variant_new_string (media_class));
+  g_variant_builder_add (&b, "{sv}",
+      "direction", g_variant_new_uint32 (direction));
   g_variant_builder_add (&b, "{sv}",
       "global-id", g_variant_new_uint32 (id));
   endpoint_props = g_variant_builder_end (&b);
diff --git a/modules/module-pw-audio-softdsp-endpoint.c b/modules/module-pw-audio-softdsp-endpoint.c
index 263962a2ae1739912b78db8c9459ba7b2a02b1b9..2d8f1c89d6b5d9bd96d6d8bc745c877168fbd8d1 100644
--- a/modules/module-pw-audio-softdsp-endpoint.c
+++ b/modules/module-pw-audio-softdsp-endpoint.c
@@ -42,9 +42,6 @@ struct _WpPwAudioSoftdspEndpoint
   GTask *init_task;
   gboolean init_abort;
 
-  /* Direction */
-  enum pw_direction direction;
-
   /* Audio Streams */
   WpAudioStream *adapter;
   GPtrArray *converters;
@@ -162,6 +159,7 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
     gpointer data)
 {
   WpPwAudioSoftdspEndpoint *self = data;
+  enum pw_direction direction = wp_endpoint_get_direction(WP_ENDPOINT(self));
   g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
   g_autofree gchar *name = NULL;
   const struct pw_node_info *adapter_info = NULL;
@@ -193,8 +191,8 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
   /* Create the audio converters */
   g_variant_iter_init (&iter, self->streams);
   for (i = 0; g_variant_iter_next (&iter, "&s", &stream); i++) {
-    wp_audio_convert_new (WP_ENDPOINT(self), i, stream, self->direction,
-        adapter_info, on_audio_convert_created, self);
+    wp_audio_convert_new (WP_ENDPOINT(self), i, stream, direction, adapter_info,
+        on_audio_convert_created, self);
 
     /* Register the stream */
     g_variant_dict_init (&d, NULL);
@@ -316,24 +314,16 @@ wp_endpoint_init_async (GAsyncInitable *initable, int io_priority,
     GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
 {
   WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (initable);
+  enum pw_direction direction = wp_endpoint_get_direction(WP_ENDPOINT(self));
   g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
-  const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self));
   GVariantDict d;
 
   /* Create the async task */
   self->init_task = g_task_new (initable, cancellable, callback, data);
 
-  /* Set the direction */
-  if (g_str_has_suffix (media_class, "Source"))
-    self->direction = PW_DIRECTION_OUTPUT;
-  else if (g_str_has_suffix (media_class, "Sink"))
-    self->direction = PW_DIRECTION_INPUT;
-  else
-    g_critical ("failed to parse direction");
-
   /* Create the adapter proxy */
   wp_audio_adapter_new (WP_ENDPOINT(self), WP_STREAM_ID_NONE, "master",
-      self->direction, self->global_id, FALSE, on_audio_adapter_created, self);
+      direction, self->global_id, FALSE, on_audio_adapter_created, self);
 
   /* Register the selected control */
   self->selected = FALSE;
@@ -401,6 +391,7 @@ endpoint_factory (WpFactory * factory, GType type, GVariant * properties,
 {
   g_autoptr (WpCore) core = NULL;
   const gchar *name, *media_class;
+  guint direction;
   guint global_id;
   g_autoptr (GVariant) streams = NULL;
 
@@ -416,6 +407,8 @@ endpoint_factory (WpFactory * factory, GType type, GVariant * properties,
       return;
   if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
       return;
+  if (!g_variant_lookup (properties, "direction", "u", &direction))
+      return;
   if (!g_variant_lookup (properties, "global-id", "u", &global_id))
       return;
   if (!(streams = g_variant_lookup_value (properties, "streams",
@@ -428,6 +421,7 @@ endpoint_factory (WpFactory * factory, GType type, GVariant * properties,
       "core", core,
       "name", name,
       "media-class", media_class,
+      "direction", direction,
       "global-id", global_id,
       "streams", streams,
       NULL);
diff --git a/modules/module-pw-audio-softdsp-endpoint/adapter.c b/modules/module-pw-audio-softdsp-endpoint/adapter.c
index 14aee5c0e9d9d734989b87ad3700c53f17ff239a..efb8ed6e884d7a5f4f65b584ebf5fa6dd9a98f24 100644
--- a/modules/module-pw-audio-softdsp-endpoint/adapter.c
+++ b/modules/module-pw-audio-softdsp-endpoint/adapter.c
@@ -26,6 +26,7 @@ struct _WpAudioAdapter
   /* The task to signal the proxy is initialized */
   GTask *init_task;
   gboolean init_abort;
+  gboolean ports_done;
 
   /* Props */
   guint adapter_id;
@@ -79,9 +80,45 @@ on_audio_adapter_done(WpProxy *proxy, gpointer data)
 {
   WpAudioAdapter *self = data;
 
+  /* Emit the ports if not done and sync again */
+  if (!self->ports_done) {
+    enum pw_direction direction =
+      wp_audio_stream_get_direction ( WP_AUDIO_STREAM (self));
+    struct pw_node_proxy *pw_proxy = NULL;
+    uint8_t buf[1024];
+    struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+    struct spa_pod *param;
+
+    /* Emit the props param */
+    pw_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy));
+    pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
+
+    /* Emit the ports */
+    if (self->convert) {
+      param = spa_pod_builder_add_object(&pod_builder,
+          SPA_TYPE_OBJECT_ParamPortConfig,  SPA_PARAM_PortConfig,
+          SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(direction),
+          SPA_PARAM_PORT_CONFIG_mode,       SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert));
+    } else {
+      struct spa_audio_info_raw format = *wp_proxy_node_get_format (self->proxy);
+      param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
+      param = spa_pod_builder_add_object(&pod_builder,
+          SPA_TYPE_OBJECT_ParamPortConfig,  SPA_PARAM_PortConfig,
+          SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(direction),
+          SPA_PARAM_PORT_CONFIG_mode,       SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
+          SPA_PARAM_PORT_CONFIG_format,     SPA_POD_Pod(param));
+    }
+    pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
+
+    /* Sync */
+    self->ports_done = TRUE;
+    wp_proxy_sync (WP_PROXY(self->proxy));
+    return;
+  }
+
   /* Don't do anything if the audio adapter has already been initialized */
   if (!self->init_task)
-      return;
+    return;
 
   /* Finish the creation of the audio adapter */
   g_task_return_boolean (self->init_task, TRUE);
@@ -93,12 +130,6 @@ on_audio_adapter_proxy_created(GObject *initable, GAsyncResult *res,
     gpointer data)
 {
   WpAudioAdapter *self = data;
-  enum pw_direction direction =
-      wp_audio_stream_get_direction ( WP_AUDIO_STREAM (self));
-  struct pw_node_proxy *pw_proxy = NULL;
-  uint8_t buf[1024];
-  struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
-  struct spa_pod *param;
 
   /* Get the adapter proxy */
   self->proxy = WP_PROXY_NODE (object_safe_new_finish (self, initable,
@@ -106,39 +137,10 @@ on_audio_adapter_proxy_created(GObject *initable, GAsyncResult *res,
   if (!self->proxy)
     return;
 
-  /* Get the pipewire proxy */
-  pw_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy));
-  g_return_if_fail (pw_proxy);
-
-  /* Emit the props param */
-  pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
-
-  /* Emit the ports */
-  if (self->convert) {
-    param = spa_pod_builder_add_object(&pod_builder,
-        SPA_TYPE_OBJECT_ParamPortConfig,  SPA_PARAM_PortConfig,
-        SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(direction),
-        SPA_PARAM_PORT_CONFIG_mode,       SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert));
-    pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
-  } else {
-    struct spa_audio_info_raw format;
-    spa_zero(format);
-    format.format = SPA_AUDIO_FORMAT_F32P;
-    format.flags = 1;
-    format.rate = 48000;
-    format.channels = 2;
-    format.position[0] = SPA_AUDIO_CHANNEL_FL;
-    format.position[1] = SPA_AUDIO_CHANNEL_FR;
-    param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
-    param = spa_pod_builder_add_object(&pod_builder,
-        SPA_TYPE_OBJECT_ParamPortConfig,  SPA_PARAM_PortConfig,
-        SPA_PARAM_PORT_CONFIG_direction,  SPA_POD_Id(direction),
-        SPA_PARAM_PORT_CONFIG_mode,       SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
-        SPA_PARAM_PORT_CONFIG_format,     SPA_POD_Pod(param));
-    pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
-  }
+  /* Emit the EnumFormat param */
+  wp_proxy_node_enum_params (self->proxy, 0, SPA_PARAM_EnumFormat, 0, -1, NULL);
 
-  /* Register a callback to know when all the adapter ports have been emitted */
+  /* Register the done callback */
   g_signal_connect_object(self->proxy, "done", (GCallback)on_audio_adapter_done,
       self, 0);
   wp_proxy_sync (WP_PROXY(self->proxy));
@@ -248,6 +250,7 @@ static void
 wp_audio_adapter_init (WpAudioAdapter * self)
 {
   self->init_abort = FALSE;
+  self->ports_done = FALSE;
 }
 
 static void
diff --git a/modules/module-pw-bluez.c b/modules/module-pw-bluez.c
index 5bca9697b08e0ecc1a795eb9c4d3fd3d6f9ff033..6b3bf526a7775aec038dc048590dfa93e34894a7 100644
--- a/modules/module-pw-bluez.c
+++ b/modules/module-pw-bluez.c
@@ -25,6 +25,7 @@ struct monitor {
 struct impl {
   WpModule *module;
   WpRemotePipewire *remote_pipewire;
+  GHashTable *registered_endpoints;
 
   /* The bluez monitor */
   struct monitor monitor;
@@ -57,6 +58,107 @@ struct node {
   struct pw_proxy *proxy;
 };
 
+static void
+on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d)
+{
+  struct impl *data = d;
+  WpEndpoint *endpoint = NULL;
+  guint global_id = 0;
+  GError *error = NULL;
+
+  /* Get the endpoint */
+  endpoint = wp_endpoint_new_finish(initable, res, NULL);
+  g_return_if_fail (endpoint);
+
+  /* Check for error */
+  if (error) {
+    g_clear_object (&endpoint);
+    g_warning ("Failed to create client endpoint: %s", error->message);
+    return;
+  }
+
+  /* Get the endpoint global id */
+  g_object_get (endpoint, "global-id", &global_id, NULL);
+  g_debug ("Created bluetooth endpoint for global id %d", global_id);
+
+  /* Register the endpoint and add it to the table */
+  wp_endpoint_register (endpoint);
+  g_hash_table_insert (data->registered_endpoints, GUINT_TO_POINTER(global_id),
+      endpoint);
+}
+
+static void
+on_node_added (WpRemotePipewire *rp, guint id, gconstpointer p, gpointer d)
+{
+  struct impl *data = d;
+  const struct spa_dict *props = p;
+  g_autoptr (WpCore) core = wp_module_get_core (data->module);
+  const gchar *name, *media_class;
+  enum pw_direction direction;
+  GVariantBuilder b;
+  g_autoptr (GVariant) endpoint_props = NULL;
+
+  /* Make sure the node has properties */
+  g_return_if_fail(props);
+
+  /* Get the media_class */
+  media_class = spa_dict_lookup(props, "media.class");
+
+  /* Get the name */
+  name = spa_dict_lookup (props, "media.name");
+  if (!name)
+    name = spa_dict_lookup (props, "node.name");
+
+  /* Only handle bluetooth nodes */
+  if (!g_str_has_prefix (name, "api.bluez5"))
+    return;
+
+  /* Get the direction */
+  if (g_str_has_prefix (media_class, "Audio/Sink")) {
+    direction = PW_DIRECTION_INPUT;
+  } else if (g_str_has_prefix (media_class, "Audio/Source")) {
+    direction = PW_DIRECTION_OUTPUT;
+  } else {
+    g_critical ("failed to parse direction");
+    return;
+  }
+
+  /* Set the properties */
+  g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&b, "{sv}",
+      "name", name ?
+      g_variant_new_take_string (g_strdup_printf ("Stream %u (%s)", id, name)) :
+      g_variant_new_take_string (g_strdup_printf ("Stream %u", id)));
+  g_variant_builder_add (&b, "{sv}",
+      "media-class", g_variant_new_string (media_class));
+  g_variant_builder_add (&b, "{sv}",
+      "direction", g_variant_new_uint32 (direction));
+  g_variant_builder_add (&b, "{sv}",
+      "global-id", g_variant_new_uint32 (id));
+  endpoint_props = g_variant_builder_end (&b);
+
+  /* Create the endpoint async */
+  wp_factory_make (core, "pipewire-simple-endpoint", WP_TYPE_ENDPOINT,
+      endpoint_props, on_endpoint_created, data);
+}
+
+static void
+on_global_removed (WpRemotePipewire *rp, guint id, gpointer d)
+{
+  struct impl *data = d;
+  WpEndpoint *endpoint = NULL;
+
+  /* Get the endpoint */
+  endpoint = g_hash_table_lookup (data->registered_endpoints,
+      GUINT_TO_POINTER(id));
+  if (!endpoint)
+    return;
+
+  /* Unregister the endpoint and remove it from the table */
+  wp_endpoint_unregister (endpoint);
+  g_hash_table_remove (data->registered_endpoints, GUINT_TO_POINTER(id));
+}
+
 static struct node *
 create_node(struct impl *impl, struct device *dev, uint32_t id,
     const struct spa_device_object_info *info)
@@ -343,6 +445,10 @@ module_destroy (gpointer data)
   impl->module = NULL;
   impl->remote_pipewire = NULL;
 
+  /* Destroy the registered endpoints table */
+  g_hash_table_unref(impl->registered_endpoints);
+  impl->registered_endpoints = NULL;
+
   /* Clean up */
   g_slice_free (struct impl, impl);
 }
@@ -365,6 +471,8 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
   impl = g_slice_new0(struct impl);
   impl->module = module;
   impl->remote_pipewire = rp;
+  impl->registered_endpoints = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, (GDestroyNotify)g_object_unref);
 
   /* Set destroy callback for impl */
   wp_module_set_destroy_callback (module, module_destroy, impl);
@@ -374,4 +482,8 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
 
   /* Start the monitor when the connected callback is triggered */
   g_signal_connect(rp, "state-changed::connected", (GCallback)start_monitor, impl);
+
+  /* Register the global added/removed callbacks */
+  g_signal_connect(rp, "global-added::node", (GCallback)on_node_added, impl);
+  g_signal_connect(rp, "global-removed", (GCallback)on_global_removed, impl);
 }