stream-manager: add publish/discover/set_permission interfaces 73/241273/9
authorJaechul Lee <jcsing.lee@samsung.com>
Wed, 19 Aug 2020 02:48:15 +0000 (11:48 +0900)
committerJaechul Lee <jcsing.lee@samsung.com>
Fri, 21 Aug 2020 06:55:09 +0000 (15:55 +0900)
method name     : SetRemotePermission
method argument : [in] string for type
                  [in] unsigned  for index
                  [in] boolean for allowed
return value    : None

method name     : DiscoverRemoteDevice
method argument : [in] boolean for enable
return value    : None

method name     : PublishLocalDevice
method argument : [in] boolean for enable
return value    : None

[Version] 13.0.27
[Issue Type] New feature

Change-Id: I6ab4a388410dcf4d9480bdc1aaaaa7202a199a9f
Signed-off-by: Jaechul Lee <jcsing.lee@samsung.com>
packaging/pulseaudio-modules-tizen.spec
src/stream-manager-dbus-priv.h
src/stream-manager-dbus.c
src/stream-manager-priv.h
src/stream-manager.c

index 06d9cea..bb99b51 100644 (file)
@@ -1,6 +1,6 @@
 Name:             pulseaudio-modules-tizen
 Summary:          Pulseaudio modules for Tizen
-Version:          13.0.26
+Version:          13.0.27
 Release:          0
 Group:            Multimedia/Audio
 License:          LGPL-2.1+
index 166c3a3..940940a 100644 (file)
 #define STREAM_MANAGER_METHOD_NAME_GET_PID_OF_LATEST_STREAM          "GetPidOfLatestStream"
 #define STREAM_MANAGER_METHOD_NAME_ACTIVATE_DUCKING                  "ActivateDucking"
 #define STREAM_MANAGER_METHOD_NAME_GET_DUCKING_STATE                 "GetDuckingState"
+#define STREAM_MANAGER_METHOD_NAME_SET_REMOTE_PERMISSION             "SetRemotePermission"
+#define STREAM_MANAGER_METHOD_NAME_DISCOVER_REMOTE_DEVICE            "DiscoverRemoteDevice"
+#define STREAM_MANAGER_METHOD_NAME_PUBLISH_LOCAL_DEVICE              "PublishLocalDevice"
 /* signal */
 #define STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED                    "VolumeChanged"
 #define STREAM_MANAGER_SIGNAL_NAME_DUCKING_STATE_CHANGED             "DuckingStateChanged"
 #define STREAM_MANAGER_SIGNAL_NAME_COMMAND                           "Command"
+#define STREAM_MANAGER_SIGNAL_NAME_REMOTE_FOUND                      "RemoteFound"
 
 enum method_handler_index {
     METHOD_HANDLER_GET_STREAM_INFO,
@@ -88,6 +92,9 @@ enum method_handler_index {
     METHOD_HANDLER_GET_PID_OF_LATEST_STREAM,
     METHOD_HANDLER_ACTIVATE_DUCKING,
     METHOD_HANDLER_GET_DUCKING_STATE,
+    METHOD_HANDLER_NAME_SET_REMOTE_PERMISSION,
+    METHOD_HANDLER_DISCOVER_REMOTE_DEVICE,
+    METHOD_HANDLER_PUBLISH_LOCAL_DEVICE,
     METHOD_HANDLER_MAX
 };
 
@@ -266,6 +273,17 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "   <arg name=\"is_ducked\" direction=\"out\" type=\"b\"/>"              \
     "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
     "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_SET_REMOTE_PERMISSION\">"   \
+    "   <arg name=\"type\" direction=\"in\" type=\"s\"/>"                    \
+    "   <arg name=\"index\" direction=\"in\" type=\"u\"/>"                   \
+    "   <arg name=\"allowed\" direction=\"in\" type=\"b\"/>"                 \
+    "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_DISCOVER_REMOTE_DEVICEE\">" \
+    "   <arg name=\"enable\" direction=\"in\" type=\"b\"/>"                  \
+    "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_PUBLISH_LOCAL_DEVICE\">"    \
+    "   <arg name=\"enable\" direction=\"in\" type=\"b\"/>"                  \
+    "  </method>"                                                            \
     "  <signal name=\"STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED\">"          \
     "   <arg name=\"direction\" type=\"s\"/>"                                \
     "   <arg name=\"volume_type\" type=\"s\"/>"                              \
@@ -275,6 +293,13 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "   <arg name=\"name\" type=\"s\"/>"                                     \
     "   <arg name=\"value\" type=\"i\"/>"                                    \
     "  </signal>"                                                            \
+    "  <signal name=\"STREAM_MANAGER_SIGNAL_NAME_REMOTE_FOUND\">"            \
+    "   <arg name=\"type\" type=\"i\"/>"                                     \
+    "   <arg name=\"index\" type=\"u\"/>"                                    \
+    "   <arg name=\"conneted\" type=\"b\"/>"                                 \
+    "   <arg name=\"remote_name\" type=\"s\"/>"                              \
+    "   <arg name=\"peer_info\" type=\"s\"/>"                                \
+    "  </signal>"                                                            \
     " </interface>"                                                          \
     " <interface name=\"org.freedesktop.DBus.Introspectable\">"              \
     "  <method name=\"Introspect\">"                                         \
@@ -286,9 +311,11 @@ pa_dbus_interface_info stream_manager_interface_info = {
 
 void send_ducking_state_changed_signal(DBusConnection *conn, const int index, const int is_ducked);
 void send_command_signal(DBusConnection *conn, const char *name, int value);
+void send_remote_found_signal(DBusConnection *conn, int type, bool connected,
+                                unsigned int index, const char *name, const char *description);
 int32_t init_sm_dbus(pa_stream_manager *m);
 void deinit_sm_dbus(pa_stream_manager *m);
 
 #endif
 
-#endif
\ No newline at end of file
+#endif
index a6e7506..0367446 100644 (file)
@@ -25,6 +25,7 @@
 
 #ifdef HAVE_DBUS
 
+#include <pulsecore/proplist-util.h>
 #include "stream-manager-priv.h"
 #include "stream-manager-dbus-priv.h"
 #include "stream-manager-volume-priv.h"
@@ -70,6 +71,9 @@ static void handle_check_stream_exist_by_pid(DBusConnection *conn, DBusMessage *
 static void handle_get_pid_of_latest_stream(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_activate_ducking(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_remote_permission(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_discover_remote_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_publish_local_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void send_volume_changed_signal(DBusConnection *conn, const char *direction, const char *volume_type, const uint32_t volume_level);
 
 static pa_dbus_arg_info get_stream_info_args[]  = { { "stream_type", "s", "in" },
@@ -179,6 +183,11 @@ static pa_dbus_arg_info activate_ducking_args[] = { { "index", "u", "in" },
 static pa_dbus_arg_info get_ducking_state_args[] = { { "index", "u", "in" },
                                                { "is_ducked", "b",  "out" },
                                                 { "ret_msg", "s", "out" } };
+static pa_dbus_arg_info set_remote_permission_args[] = { { "type", "s", "in" },
+                                                   { "index", "u",  "in" },
+                                                   { "allowed", "b", "in" } };
+static pa_dbus_arg_info discover_remote_device_args[] = { { "enable", "b", "in" } };
+static pa_dbus_arg_info publish_local_device_args[] = { { "enable", "b", "in" } };
 
 static const char* signature_args_for_in[] = {
     "s",        /* METHOD_HANDLER_GET_STREAM_INFO */
@@ -208,7 +217,10 @@ static const char* signature_args_for_in[] = {
     "uss",      /* METHOD_HANDLER_CHECK_STREAM_EXIST_BY_PID */
     "sas",      /* METHOD_HANDLER_GET_PID_OF_LATEST_STREAM */
     "ubsud",    /* METHOD_HANDLER_ACTIVATE_DUCKING */
-    "u"         /* METHOD_HANDLER_GET_DUCKING_STATE */
+    "u",        /* METHOD_HANDLER_GET_DUCKING_STATE */
+    "sub",      /* METHOD_HANDLER_SET_REMOTE_PERMISSION */
+    "b",        /* METHOD_HANDLER_DISCOVER_REMOTE_DEVICE */
+    "b"         /* METHOD_HANDLER_PUBLISH_LOCAL_DEVICE */
     };
 
 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
@@ -352,6 +364,21 @@ static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
         .arguments = get_ducking_state_args,
         .n_arguments = sizeof(get_ducking_state_args) / sizeof(pa_dbus_arg_info),
         .receive_cb = handle_get_ducking_state },
+    [METHOD_HANDLER_NAME_SET_REMOTE_PERMISSION] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_SET_REMOTE_PERMISSION,
+        .arguments = set_remote_permission_args,
+        .n_arguments = sizeof(set_remote_permission_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_set_remote_permission },
+    [METHOD_HANDLER_DISCOVER_REMOTE_DEVICE] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_DISCOVER_REMOTE_DEVICE,
+        .arguments = discover_remote_device_args,
+        .n_arguments = sizeof(discover_remote_device_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_discover_remote_device },
+    [METHOD_HANDLER_PUBLISH_LOCAL_DEVICE] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_PUBLISH_LOCAL_DEVICE,
+        .arguments = publish_local_device_args,
+        .n_arguments = sizeof(publish_local_device_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_publish_local_device },
 };
 
 static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) {
@@ -2322,6 +2349,175 @@ static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, voi
     dbus_message_unref(reply);
 }
 
+static void handle_set_remote_permission(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    dbus_uint32_t index;
+    dbus_bool_t allowed;
+    pa_proplist *p = NULL;
+    char *type = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &type,
+                                       DBUS_TYPE_UINT32, &index,
+                                       DBUS_TYPE_BOOLEAN, &allowed,
+                                       DBUS_TYPE_INVALID));
+
+    if (!type) {
+        pa_log_error("invalid arguments");
+        goto out;
+    }
+
+    pa_log_info("type(%s), index(%d), allowed(%d)", type, index, allowed);
+
+    p = pa_proplist_new();
+    if (!p) {
+        pa_log_error("failed to create proplist");
+        goto out;
+    }
+
+    if (pa_proplist_set_remote_access_permission(p, allowed)) {
+         pa_log_error("set remote access permission error");
+         goto out;
+    }
+
+    if (pa_streq(type, "sink-input")) {
+        pa_sink_input *i;
+
+        i = pa_idxset_get_by_index(m->core->sink_inputs, index);
+        if (!i) {
+            pa_log_error("not found sink-input");
+            goto out;
+        }
+
+        if (pa_proplist_remote_is_allowed(i->proplist) != allowed){
+            pa_sink_input_update_proplist(i, PA_UPDATE_REPLACE, p);
+            pa_sink_input_send_event(i, PA_STREAM_EVENT_UPDATE_MEDIA_REMOTE_ACCESS, p);
+        }
+
+    } else if (pa_streq(type, "source-output")) {
+        pa_source_output *o;
+
+        o = pa_idxset_get_by_index(m->core->source_outputs, index);
+        if (!o) {
+            pa_log_error("not found source-output");
+            goto out;
+        }
+
+        if (pa_proplist_remote_is_allowed(o->proplist) != allowed){
+            pa_source_output_update_proplist(o, PA_UPDATE_REPLACE, p);
+            pa_source_output_send_event(o, PA_STREAM_EVENT_UPDATE_MEDIA_REMOTE_ACCESS, p);
+        }
+
+    } else {
+        pa_log_warn("unknown type");
+    }
+
+out:
+    if (p)
+        pa_proplist_free(p);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_discover_remote_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    pa_module *module;
+    dbus_bool_t enable;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_BOOLEAN, &enable,
+                                       DBUS_TYPE_INVALID));
+
+    pa_log_info("discover module enable(%d)", enable);
+
+    if (enable) {
+        if (m->m_discover) {
+            pa_log_error("already loaded");
+            goto error;
+        }
+
+        if (pa_module_load(&module, m->core, "module-tizenaudio-discover", NULL)) {
+            pa_log_error("failed to load module");
+            goto error;
+        }
+        m->m_discover = module->index;
+    } else {
+        if (m->m_discover) {
+            pa_module_unload_request_by_index(m->core, m->m_discover, true);
+            m->m_discover = 0;
+        }
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    return;
+
+error:
+    pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "%s",
+                            "org.tizen.multimedia.audio.Internal");
+}
+
+static void handle_publish_local_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    pa_module *module;
+    dbus_bool_t enable;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_BOOLEAN, &enable,
+                                       DBUS_TYPE_INVALID));
+
+    pa_log_info("publish module enable(%d)", enable);
+
+    if (enable) {
+        if (m->m_protocol_tcp || m->m_publish) {
+            pa_log_error("already loaded");
+            goto error;
+        }
+
+        if (pa_module_load(&module, m->core, "module-native-protocol-tcp", "auth-anonymous=1")) {
+            pa_log_error("failed to load module");
+            goto error;
+        }
+        m->m_protocol_tcp = module->index;
+
+        if (pa_module_load(&module, m->core, "module-tizenaudio-publish", NULL)) {
+            pa_module_unload_request_by_index(m->core, m->m_protocol_tcp, true);
+            pa_log_error("failed to load module");
+            goto error;
+        }
+        m->m_publish = module->index;
+    } else {
+        if (m->m_protocol_tcp) {
+            pa_module_unload_request_by_index(m->core, m->m_protocol_tcp, true);
+            m->m_protocol_tcp = 0;
+        }
+        if (m->m_publish) {
+            pa_module_unload_request_by_index(m->core, m->m_publish, true);
+            m->m_publish = 0;
+        }
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    return;
+
+error:
+    pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "%s",
+                            "org.tizen.multimedia.audio.Internal");
+}
+
 static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) {
     int idx = 0;
     pa_stream_manager *m = (pa_stream_manager*)userdata;
@@ -2435,6 +2631,34 @@ void send_command_signal(DBusConnection *conn, const char *name, int value) {
     dbus_message_unref(signal_msg);
 }
 
+void send_remote_found_signal(DBusConnection *conn, int type, bool connected, unsigned int index,
+                                                      const char *name, const char *description) {
+    DBusMessage *signal_msg;
+    DBusMessageIter msg_iter;
+    dbus_bool_t c = (dbus_bool_t)connected;
+
+    pa_assert(conn);
+
+    if (!name || !description) {
+        pa_log_error("Unknown device");
+        return;
+    }
+
+    pa_log_info("type[%d], index[%d], connected[%d], name[%s] description[%s]", type, index, connected, name, description);
+
+    pa_assert_se((signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, STREAM_MANAGER_SIGNAL_NAME_REMOTE_FOUND)));
+    dbus_message_iter_init_append(signal_msg, &msg_iter);
+
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, &type);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_UINT32, &index);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_BOOLEAN, &c);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &description);
+
+    pa_assert_se(dbus_connection_send(conn, signal_msg, NULL));
+    dbus_message_unref(signal_msg);
+}
+
 int32_t init_sm_dbus(pa_stream_manager *m) {
     pa_assert(m);
 
index f78a063..aeb0ac9 100644 (file)
@@ -266,7 +266,16 @@ struct _stream_manager {
         *source_output_unlink_slot,
         *source_output_state_changed_slot,
         *source_output_move_start_slot,
-        *source_output_move_finish_slot;
+        *source_output_move_finish_slot,
+
+        *remote_sink_input_put_slot,
+        *remote_sink_input_unlink_slot,
+        *remote_source_output_unlink_slot,
+        *remote_source_output_put_slot;
+
+    uint32_t m_discover;
+    uint32_t m_protocol_tcp;
+    uint32_t m_publish;
 
 #ifdef HAVE_DBUS
 #ifdef USE_DBUS_PROTOCOL
index f75e8f7..8073580 100644 (file)
@@ -2731,6 +2731,53 @@ static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_ou
     return PA_HOOK_OK;
 }
 
+static void notify_remote_connection(pa_core *core, pa_object *obj, pa_stream_manager *m, bool connected) {
+    pa_proplist *p;
+    pa_sink_input *i;
+    pa_source_output *o;
+    unsigned int index;
+
+    if (pa_source_output_isinstance(obj)) {
+        o = PA_SOURCE_OUTPUT(obj);
+        p = o->proplist;
+        index = o->index;
+    } else {
+        i = PA_SINK_INPUT(obj);
+        p = i->proplist;
+        index = i->index;
+    }
+
+    if (!p) {
+        pa_log_error("unknown remote client");
+        return;
+    }
+
+    if (pa_proplist_has_remote_name(p)) {
+        send_remote_found_signal(pa_dbus_connection_get(m->dbus_conn),
+                pa_source_output_isinstance(obj) ? 1 : 0, connected, index,
+                pa_proplist_gets(p, PA_PROP_MEDIA_REMOTE_NAME),
+                pa_proplist_gets(p, "native-protocol.peer"));
+    }
+}
+
+static pa_hook_result_t remote_client_put_cb(pa_core *core, pa_object *o, pa_stream_manager *m) {
+    pa_core_assert_ref(core);
+    pa_object_assert_ref(o);
+
+    notify_remote_connection(core, o, m, true);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t remote_client_unlink_cb(pa_core *core, pa_object *o, pa_stream_manager *m) {
+    pa_core_assert_ref(core);
+    pa_object_assert_ref(o);
+
+    notify_remote_connection(core, o, m, false);
+
+    return PA_HOOK_OK;
+}
+
 static void find_next_device_for_auto_route(pa_stream_manager *m, stream_route_type_t route_type, const char *stream_role,
                                         stream_type_t stream_type, const char *cur_device_type, const char *preferred_device_role, pa_tz_device **next_device) {
     stream_info *si = NULL;
@@ -3734,6 +3781,11 @@ pa_stream_manager* pa_stream_manager_get(pa_core *c) {
     m->source_output_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_move_start_cb, m);
     m->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_move_finish_cb, m);
 
+    m->remote_sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) remote_client_put_cb, m);
+    m->remote_sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) remote_client_unlink_cb, m);
+    m->remote_source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) remote_client_put_cb, m);
+    m->remote_source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) remote_client_unlink_cb, m);
+
     m->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CLIENT | PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, (pa_subscription_cb_t)subscribe_cb, m);
 
     m->comm.comm = pa_communicator_get(c);
@@ -3787,8 +3839,12 @@ static void free_hook_slots(pa_stream_manager *m) {
         pa_hook_slot_free(m->sink_input_new_slot);
     if (m->sink_input_put_slot)
         pa_hook_slot_free(m->sink_input_put_slot);
+    if (m->remote_sink_input_put_slot)
+        pa_hook_slot_free(m->remote_sink_input_put_slot);
     if (m->sink_input_unlink_slot)
         pa_hook_slot_free(m->sink_input_unlink_slot);
+    if (m->remote_sink_input_unlink_slot)
+        pa_hook_slot_free(m->remote_sink_input_unlink_slot);
     if (m->sink_input_state_changed_slot)
         pa_hook_slot_free(m->sink_input_state_changed_slot);
     if (m->sink_input_move_start_slot)
@@ -3797,12 +3853,17 @@ static void free_hook_slots(pa_stream_manager *m) {
         pa_hook_slot_free(m->sink_input_move_finish_slot);
     if (m->sink_input_ramp_finish_slot)
         pa_hook_slot_free(m->sink_input_ramp_finish_slot);
+
     if (m->source_output_new_slot)
         pa_hook_slot_free(m->source_output_new_slot);
     if (m->source_output_put_slot)
         pa_hook_slot_free(m->source_output_put_slot);
+    if (m->remote_source_output_put_slot)
+        pa_hook_slot_free(m->remote_source_output_put_slot);
     if (m->source_output_unlink_slot)
         pa_hook_slot_free(m->source_output_unlink_slot);
+    if (m->remote_source_output_unlink_slot)
+        pa_hook_slot_free(m->remote_source_output_unlink_slot);
     if (m->source_output_state_changed_slot)
         pa_hook_slot_free(m->source_output_state_changed_slot);
     if (m->source_output_move_start_slot)