diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c
index f2c3d26f24dc785a2f0243619c4dc75d5661f1d9..7f94cd30fff53816862d858f846cf7d4233ab631 100644
--- a/modules/module-pipewire/simple-endpoint.c
+++ b/modules/module-pipewire/simple-endpoint.c
@@ -15,12 +15,19 @@
 
 #include <wp/wp.h>
 #include <pipewire/pipewire.h>
+#include <spa/pod/parser.h>
+#include <spa/param/props.h>
 
 struct _WpPipewireSimpleEndpoint
 {
   WpEndpoint parent;
   struct pw_node_proxy *node;
   struct spa_hook proxy_listener;
+  struct spa_hook node_proxy_listener;
+
+  /* controls cache */
+  gfloat volume;
+  gboolean mute;
 };
 
 enum {
@@ -28,20 +35,125 @@ enum {
   PROP_NODE_PROXY,
 };
 
+enum {
+  CONTROL_VOLUME = 0,
+  CONTROL_MUTE,
+};
+
 G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpoint,
     simple_endpoint, WP_PIPEWIRE, SIMPLE_ENDPOINT, WpEndpoint)
 
 G_DEFINE_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_TYPE_ENDPOINT)
 
+static void
+node_proxy_destroy (void *data)
+{
+  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (data);
+  self->node = NULL;
+  wp_endpoint_unregister (WP_ENDPOINT (self));
+}
+
+static const struct pw_proxy_events node_proxy_events = {
+  PW_VERSION_PROXY_EVENTS,
+  .destroy = node_proxy_destroy,
+};
+
+static void
+node_proxy_param (void *object, int seq, uint32_t id,
+    uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
+
+  switch (id) {
+    case SPA_PARAM_Props:
+    {
+      struct spa_pod_prop *prop;
+      struct spa_pod_object *obj = (struct spa_pod_object *) param;
+      float volume = self->volume;
+      bool mute = self->mute;
+
+      SPA_POD_OBJECT_FOREACH(obj, prop) {
+        switch (prop->key) {
+        case SPA_PROP_volume:
+          spa_pod_get_float(&prop->value, &volume);
+          break;
+        case SPA_PROP_mute:
+          spa_pod_get_bool(&prop->value, &mute);
+          break;
+        default:
+          break;
+        }
+      }
+
+      g_debug ("WpEndpoint:%p param event, vol:(%lf -> %f) mute:(%d -> %d)",
+          self, self->volume, volume, self->mute, mute);
+
+      if (self->volume != volume) {
+        self->volume = volume;
+        wp_endpoint_notify_control_value (WP_ENDPOINT (self), CONTROL_VOLUME);
+      }
+      if (self->mute != mute) {
+        self->mute = mute;
+        wp_endpoint_notify_control_value (WP_ENDPOINT (self), CONTROL_MUTE);
+      }
+
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+static const struct pw_node_proxy_events node_node_proxy_events = {
+  PW_VERSION_NODE_PROXY_EVENTS,
+  .param = node_proxy_param,
+};
+
 static void
 simple_endpoint_init (WpPipewireSimpleEndpoint * self)
 {
-  GVariantBuilder b;
+}
+
+static void
+simple_endpoint_constructed (GObject * object)
+{
+  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
+  GVariantDict d;
+  uint32_t ids[1] = { SPA_PARAM_Props };
+  uint32_t n_ids = 1;
+
+  pw_proxy_add_listener ((struct pw_proxy *) self->node, &self->proxy_listener,
+      &node_proxy_events, self);
+  pw_node_proxy_add_listener (self->node, &self->node_proxy_listener,
+      &node_node_proxy_events, self);
+  pw_node_proxy_subscribe_params (self->node, ids, n_ids);
+
+  g_variant_dict_init (&d, NULL);
+  g_variant_dict_insert (&d, "id", "u", 0);
+  g_variant_dict_insert (&d, "name", "s", "default");
+  wp_endpoint_register_stream (WP_ENDPOINT (self), g_variant_dict_end (&d));
 
-  g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
-  g_variant_builder_add (&b, "{sv}", "id", g_variant_new_uint32 (0));
-  g_variant_builder_add (&b, "{sv}", "name", g_variant_new_string ("default"));
-  wp_endpoint_register_stream (WP_ENDPOINT (self), g_variant_builder_end (&b));
+  /* Audio streams have volume & mute controls */
+  if (g_strrstr (wp_endpoint_get_media_class (WP_ENDPOINT (self)), "Audio")) {
+    g_variant_dict_init (&d, NULL);
+    g_variant_dict_insert (&d, "id", "u", CONTROL_VOLUME);
+    g_variant_dict_insert (&d, "stream-id", "u", 0);
+    g_variant_dict_insert (&d, "name", "s", "volume");
+    g_variant_dict_insert (&d, "type", "s", "d");
+    g_variant_dict_insert (&d, "range", "(dd)", 0.0, 1.0);
+    g_variant_dict_insert (&d, "default-value", "d", 1.0);
+    wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d));
+
+    g_variant_dict_init (&d, NULL);
+    g_variant_dict_insert (&d, "id", "u", CONTROL_MUTE);
+    g_variant_dict_insert (&d, "stream-id", "u", 0);
+    g_variant_dict_insert (&d, "name", "s", "mute");
+    g_variant_dict_insert (&d, "type", "s", "b");
+    g_variant_dict_insert (&d, "default-value", "b", FALSE);
+    wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d));
+  }
+
+  G_OBJECT_CLASS (simple_endpoint_parent_class)->constructed (object);
 }
 
 static void
@@ -99,17 +211,83 @@ simple_endpoint_prepare_link (WpEndpoint * self, guint32 stream_id,
   return TRUE;
 }
 
+static GVariant *
+simple_endpoint_get_control_value (WpEndpoint * ep, guint32 control_id)
+{
+  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep);
+
+  switch (control_id) {
+    case CONTROL_VOLUME:
+      return g_variant_new_double (self->volume);
+    case CONTROL_MUTE:
+      return g_variant_new_boolean (self->mute);
+    default:
+      g_warning ("Unknown control id %u", control_id);
+      return NULL;
+  }
+}
+
+static gboolean
+simple_endpoint_set_control_value (WpEndpoint * ep, guint32 control_id,
+    GVariant * value)
+{
+  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep);
+  char buf[1024];
+  struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+  float volume;
+  bool mute;
+
+  switch (control_id) {
+    case CONTROL_VOLUME:
+      volume = g_variant_get_double (value);
+
+      g_debug("WpEndpoint:%p set volume control (%u) value, vol:%f", self,
+          control_id, volume);
+
+      pw_node_proxy_set_param (self->node,
+          SPA_PARAM_Props, 0,
+          spa_pod_builder_add_object (&b,
+              SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
+              SPA_PROP_volume, SPA_POD_Float(volume),
+              NULL));
+      break;
+
+    case CONTROL_MUTE:
+      mute = g_variant_get_boolean (value);
+
+      g_debug("WpEndpoint:%p set mute control (%u) value, mute:%d", self,
+          control_id, mute);
+
+      pw_node_proxy_set_param (self->node,
+          SPA_PARAM_Props, 0,
+          spa_pod_builder_add_object (&b,
+              SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
+              SPA_PROP_mute, SPA_POD_Bool(mute),
+              NULL));
+      break;
+
+    default:
+      g_warning ("Unknown control id %u", control_id);
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
 static void
 simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass)
 {
   GObjectClass *object_class = (GObjectClass *) klass;
   WpEndpointClass *endpoint_class = (WpEndpointClass *) klass;
 
+  object_class->constructed = simple_endpoint_constructed;
   object_class->finalize = simple_endpoint_finalize;
   object_class->set_property = simple_endpoint_set_property;
   object_class->get_property = simple_endpoint_get_property;
 
   endpoint_class->prepare_link = simple_endpoint_prepare_link;
+  endpoint_class->get_control_value = simple_endpoint_get_control_value;
+  endpoint_class->set_control_value = simple_endpoint_set_control_value;
 
   g_object_class_install_property (object_class, PROP_NODE_PROXY,
       g_param_spec_pointer ("node-proxy", "node-proxy",
@@ -117,19 +295,6 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass)
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 }
 
-static void
-node_proxy_destroy (void *data)
-{
-  WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (data);
-  self->node = NULL;
-  wp_endpoint_unregister (WP_ENDPOINT (self));
-}
-
-static const struct pw_proxy_events node_proxy_events = {
-  PW_VERSION_PROXY_EVENTS,
-  .destroy = node_proxy_destroy,
-};
-
 gpointer
 simple_endpoint_factory (WpFactory * factory, GType type,
     GVariant * properties)
@@ -157,8 +322,5 @@ simple_endpoint_factory (WpFactory * factory, GType type,
       "node-proxy", (gpointer) proxy,
       NULL);
 
-  pw_proxy_add_listener ((gpointer) proxy, &ep->proxy_listener,
-      &node_proxy_events, ep);
-
   return ep;
 }