]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
app_confbridge: Enable sending events to participants
authorGeorge Joseph <gjoseph@digium.com>
Thu, 31 May 2018 21:22:13 +0000 (15:22 -0600)
committerGeorge Joseph <gjoseph@digium.com>
Wed, 13 Jun 2018 15:11:25 +0000 (09:11 -0600)
ConfBridge can now send events to participants via in-dialog MESSAGEs.
All current Confbridge events are supported, such as ConfbridgeJoin,
ConfbridgeLeave, etc.  In addition to those events, a new event
ConfbridgeWelcome has been added that will send a list of all
current participants to a new participant.

For all but the ConfbridgeWelcome event, the JSON message contains
information about the bridge, such as its id and name, and information
about the channel that triggered the event such as channel name,
callerid info, mute status, and the MSID labels for their audio and
video tracks. You can use the labels to correlate callerid and mute
status to specific video elements in a webrtc client.

To control this behavior, the following options have been added to
confbridge.conf:

bridge_profile/enable_events:  This must be enabled on any bridge where
events are desired.

user_profile/send_events:  This must be set for a user profile to send
events.  Different user profiles connected to the same bridge can have
different settings.  This allows admins to get events but not normal
users for instance.

user_profile/echo_events:  In some cases, you might not want the user
triggering the event to get the event sent back to them.  To prevent it,
set this to false.

A change was also made to res_pjsip_sdp_rtp to save the generated msid
to the stream so it can be re-used.  This allows participant A's video
stream to appear as the same label to all other participants.

Change-Id: I26420aa9f101f0b2387dc9e2fd10733197f1318e

CHANGES
apps/app_confbridge.c
apps/confbridge/conf_config_parser.c
apps/confbridge/confbridge_manager.c
apps/confbridge/include/confbridge.h
configs/samples/confbridge.conf.sample
res/res_pjsip_sdp_rtp.c

diff --git a/CHANGES b/CHANGES
index 6f57c23988d32aa6de115c0219609c85612f7375..0a510afd491724f8d2b481adbf82340aea778059 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -23,16 +23,22 @@ Core
 
 app_sendtext
 ------------------
   Support Enhanced Messaging.  SendText now accepts new channel variables
-    that can be used to override the To and From display names and set the
-    Content-Type of a message.  Since you can now set Content-Type, other
-    text/* content types are now valid.
* Support Enhanced Messaging.  SendText now accepts new channel variables
+   that can be used to override the To and From display names and set the
+   Content-Type of a message.  Since you can now set Content-Type, other
+   text/* content types are now valid.
 
 app_confbridge
 ------------------
-  * ConfbridgeList now shows talking status. This utilizes the same voice
-    detection as the ConfbridgeTalking event, so bridges must be configured
-    with "talk_detection_events=yes" for this flag to have meaning.
+ * ConfbridgeList now shows talking status. This utilizes the same voice
+   detection as the ConfbridgeTalking event, so bridges must be configured
+   with "talk_detection_events=yes" for this flag to have meaning.
+
+ * ConfBridge can now send events to participants via in-dialog MESSAGEs.
+   All current Confbridge events are supported, such as ConfbridgeJoin,
+   ConfbridgeLeave, etc.  In addition to those events, a new event
+   ConfbridgeWelcome has been added that will send a list of all
+   current participants to a new participant.
 
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 15.3.0 to Asterisk 15.4.0 ------------
index 177429bd80cea5db9866d6e58aecc1d56cac4ef8..76806039dcbfabb51bf9bdc45b7979f6b7e1b497 100644 (file)
@@ -71,6 +71,8 @@
 #include "asterisk/json.h"
 #include "asterisk/format_cache.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/stream.h"
+#include "asterisk/message.h"
 
 /*** DOCUMENTATION
        <application name="ConfBridge" language="en_US">
@@ -547,6 +549,315 @@ const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds
        return "";
 }
 
+
+static struct ast_json *channel_to_json(struct ast_channel_snapshot *channel_snapshot,
+       struct ast_json *conf_blob, struct ast_json *labels_blob)
+{
+       struct ast_json *json_channel = ast_channel_snapshot_to_json(channel_snapshot, NULL);
+
+       if (!json_channel) {
+               return NULL;
+       }
+
+       /* These items are removed for privacy reasons. */
+       ast_json_object_del(json_channel, "dialplan");
+       ast_json_object_del(json_channel, "connected");
+       ast_json_object_del(json_channel, "accountcode");
+
+       /* conf_blob contains flags such as talking, admin, mute, etc. */
+       if (conf_blob) {
+               struct ast_json *conf_copy = ast_json_copy(conf_blob);
+
+               if (!conf_copy) {
+                       ast_json_unref(json_channel);
+                       return NULL;
+               }
+               ast_json_object_del(conf_copy, "conference");
+               ast_json_object_update(json_channel, conf_copy);
+               ast_json_unref(conf_copy);
+       }
+
+       /* labels_blob contains the msid labels to correlate to streams. */
+       if (labels_blob) {
+               ast_json_object_update(json_channel, labels_blob);
+       }
+
+       return json_channel;
+}
+
+static struct ast_json *bridge_to_json(struct ast_bridge_snapshot *bridge_snapshot)
+{
+       struct ast_json *json_bridge = ast_bridge_snapshot_to_json(bridge_snapshot, NULL);
+
+       if (!json_bridge) {
+               return NULL;
+       }
+
+       /* These items have no use in the context of bridge participant info. */
+       ast_json_object_del(json_bridge, "technology");
+       ast_json_object_del(json_bridge, "bridge_type");
+       ast_json_object_del(json_bridge, "bridge_class");
+       ast_json_object_del(json_bridge, "creator");
+       ast_json_object_del(json_bridge, "channels");
+
+       return json_bridge;
+}
+
+static struct ast_json *pack_bridge_and_channels(
+       struct ast_json *json_bridge, struct ast_json *json_channels,
+       struct stasis_message * msg)
+{
+       const struct timeval *tv = stasis_message_timestamp(msg);
+       const char *msg_name = confbridge_event_type_to_string(stasis_message_type(msg));
+       const char *fmt = ast_json_typeof(json_channels) == AST_JSON_ARRAY ?
+               "{s: s, s: o, s: o, s: o }" : "{s: s, s: o, s: o, s: [ o ] }";
+
+       return ast_json_pack(fmt,
+               "type", msg_name,
+               "timestamp", ast_json_timeval(*tv, NULL),
+               "bridge", json_bridge,
+               "channels", json_channels);
+}
+
+static struct ast_json *pack_snapshots(        struct ast_bridge_snapshot *bridge_snapshot,
+       struct ast_channel_snapshot *channel_snapshot,  struct ast_json *conf_blob,
+       struct ast_json *labels_blob, struct stasis_message * msg)
+{
+       struct ast_json *json_bridge;
+       struct ast_json *json_channel;
+
+       json_bridge = bridge_to_json(bridge_snapshot);
+       json_channel = channel_to_json(channel_snapshot, conf_blob, labels_blob);
+
+       return pack_bridge_and_channels(json_bridge, json_channel, msg);
+}
+
+enum label_direction {
+       LABEL_DIRECTION_SRC,
+       LABEL_DIRECTION_DEST,
+};
+
+static struct ast_stream *get_stream(struct ast_stream_topology *topology,
+       enum ast_media_type m_type)
+{
+       int count;
+       int i;
+
+       count = ast_stream_topology_get_count(topology);
+       if (count < 0) {
+               return NULL;
+       }
+
+       for (i = 0; i < count; i++) {
+               struct ast_stream *s;
+               enum ast_stream_state s_state;
+               enum ast_media_type s_type;
+
+               s = ast_stream_topology_get_stream(topology, i);
+               s_state = ast_stream_get_state(s);
+               s_type = ast_stream_get_type(s);
+               if (s_type == m_type
+                       && (s_state == AST_STREAM_STATE_SENDRECV || s_state == AST_STREAM_STATE_RECVONLY)) {
+                       return s;
+               }
+       }
+
+       return NULL;
+}
+
+static struct ast_json *get_media_labels(struct confbridge_conference *conference,
+       struct ast_channel *src_chan, struct ast_channel *dest_chan, enum label_direction dir)
+{
+       struct ast_stream_topology *topology;
+       struct ast_stream *stream;
+       const char *curr_a_label;
+       const char *a_label = NULL;
+       const char *v_label = NULL;
+       struct ast_json *labels = ast_json_array_create();
+
+       if (!labels) {
+               return NULL;
+       }
+
+       topology = ast_channel_get_stream_topology(dir == LABEL_DIRECTION_SRC ? src_chan : dest_chan);
+       stream = get_stream(topology, AST_MEDIA_TYPE_AUDIO);
+       curr_a_label = stream ? ast_stream_get_metadata(stream, "MSID:LABEL") : NULL;
+       a_label = curr_a_label ?: conference->bridge->uniqueid;
+       ast_json_array_append(labels, ast_json_string_create(a_label));
+
+       topology = ast_channel_get_stream_topology(dir == LABEL_DIRECTION_SRC ? dest_chan : src_chan);
+       stream = get_stream(topology, AST_MEDIA_TYPE_VIDEO);
+       v_label = stream ? ast_stream_get_metadata(stream, "MSID:LABEL") : NULL;
+       if (v_label) {
+               ast_json_array_append(labels, ast_json_string_create(v_label));
+       }
+
+       return ast_json_pack("{s: o }", "media_source_track_labels", labels);
+}
+
+static void send_message(const char *msg_name, char *conf_name, struct ast_json *json_object,
+       struct ast_channel *chan)
+{
+       struct ast_msg_data *data_msg;
+       struct ast_msg_data_attribute attrs[] = {
+               { .type = AST_MSG_DATA_ATTR_FROM, conf_name },
+               { .type = AST_MSG_DATA_ATTR_CONTENT_TYPE, .value = "application/x-asterisk-confbridge-event+json"},
+               { .type = AST_MSG_DATA_ATTR_BODY, },
+       };
+       char *json;
+       int rc = 0;
+
+       json = ast_json_dump_string_format(json_object, AST_JSON_PRETTY);
+       if (!json) {
+               ast_log(LOG_ERROR, "Unable to convert json_object for %s message to string\n", msg_name);
+               return;
+       }
+       attrs[2].value = json;
+
+       data_msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG, attrs, ARRAY_LEN(attrs));
+       if (!data_msg) {
+               ast_log(LOG_ERROR, "Unable to create %s message for channel '%s'\n", msg_name,
+                       ast_channel_name(chan));
+               ast_json_free(json);
+               return;
+       }
+
+       rc = ast_sendtext_data(chan, data_msg);
+       ast_free(data_msg);
+       if (rc != 0) {
+               /* Don't complain if we can't send a leave message. The channel is probably gone. */
+               if (strcmp(confbridge_event_type_to_string(confbridge_leave_type()), msg_name) != 0) {
+                       ast_log(LOG_ERROR, "Failed to queue %s message to '%s'\n%s\n", msg_name,
+                               ast_channel_name(chan), json);
+               }
+               ast_json_free(json);
+               return;
+       }
+
+       ast_debug(3, "Queued %s message to '%s'\n%s\n", msg_name, ast_channel_name(chan), json);
+       ast_json_free(json);
+}
+
+static void send_event_to_participants(struct confbridge_conference *conference,
+       struct ast_channel *chan, struct stasis_message * msg)
+{
+       struct ast_bridge_blob *obj = stasis_message_data(msg);
+       struct ast_json *extras = obj->blob;
+       struct user_profile u_profile = {{0}};
+       int source_send_events = 0;
+       int source_echo_events = 0;
+       struct ast_json* json_channels = NULL;
+       struct confbridge_user *user;
+       const char *msg_name = confbridge_event_type_to_string(stasis_message_type(msg));
+
+       ast_debug(3, "Distributing %s event to participants\n", msg_name);
+
+       /* This could be a channel level event or a bridge level event */
+       if (chan) {
+               if (!conf_find_user_profile(chan, NULL, &u_profile)) {
+                       ast_log(LOG_ERROR, "Unable to retrieve user profile for channel '%s'\n",
+                               ast_channel_name(chan));
+                       return;
+               }
+               source_send_events = ast_test_flag(&u_profile, USER_OPT_SEND_EVENTS);
+               source_echo_events = ast_test_flag(&u_profile, USER_OPT_ECHO_EVENTS);
+               ast_debug(3, "send_events: %d  echo_events: %d for profile %s\n",
+                       source_send_events, source_echo_events, u_profile.name);
+       }
+
+       /* Now send a message to the participants with the json string. */
+       ao2_lock(conference);
+       AST_LIST_TRAVERSE(&conference->active_list, user, list) {
+               struct ast_json *json_object;
+               struct ast_json* source_json_labels = NULL;
+
+               /*
+                * If the msg type is join, we need to capture all targets channel info so we can
+                * send a welcome message to the source channel with all current participants.
+                */
+               if (source_send_events && stasis_message_type(msg) == confbridge_join_type()) {
+                       struct ast_channel_snapshot *target_snapshot;
+                       struct ast_json *target_json_channel;
+                       struct ast_json *target_json_labels;
+
+                       target_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(user->chan));
+                       if (!target_snapshot) {
+                               ast_log(LOG_ERROR, "Unable to get a channel snapshot for '%s'\n",
+                                       ast_channel_name(user->chan));
+                               continue;
+                       }
+
+                       target_json_labels = get_media_labels(conference, chan, user->chan, LABEL_DIRECTION_SRC);
+                       target_json_channel = channel_to_json(target_snapshot, extras, target_json_labels);
+                       ao2_ref(target_snapshot, -1);
+                       ast_json_unref(target_json_labels);
+
+                       if (!json_channels) {
+                               json_channels = ast_json_array_create();
+                               if (!json_channels) {
+                                       ast_log(LOG_ERROR, "Unable to allocate json array\n");
+                                       ast_json_unref(target_json_channel);
+                                       ast_json_unref(target_json_labels);
+                                       return;
+                               }
+                       }
+
+                       ast_json_array_append(json_channels, target_json_channel);
+               }
+
+               /* Don't send a message to the user that triggered the event. */
+               if (!source_echo_events && user->chan == chan) {
+                       ast_debug(3, "Skipping queueing %s message to '%s'. Same channel.\n", msg_name,
+                               ast_channel_name(user->chan));
+                       continue;
+               }
+
+               /* Don't send a message to users in profiles not sending events. */
+               if (!ast_test_flag(&user->u_profile, USER_OPT_SEND_EVENTS)) {
+                       ast_debug(3, "Skipping queueing %s message to '%s'. Not receiving events.\n", msg_name,
+                               ast_channel_name(user->chan));
+                       continue;
+               }
+
+               source_json_labels = get_media_labels(conference, chan, user->chan, LABEL_DIRECTION_DEST);
+               ast_json_object_update(extras, source_json_labels);
+
+               json_object = pack_snapshots(obj->bridge, obj->channel, extras, source_json_labels, msg);
+               ast_json_unref(source_json_labels);
+
+               if (!json_object) {
+                       ast_log(LOG_ERROR, "Unable to convert %s message to json\n", msg_name);
+                       continue;
+               }
+
+               send_message(msg_name, conference->name, json_object, user->chan);
+               ast_json_unref(json_object);
+       }
+       ao2_unlock(conference);
+
+       /*
+        * If this is a join event, send the welcome message to just the joining user
+        * if it's not audio-only or otherwise restricted.
+        */
+       if (source_send_events && json_channels
+               && stasis_message_type(msg) == confbridge_join_type()) {
+               struct ast_json *json_object;
+               struct ast_json *json_bridge;
+               const char *welcome_msg_name = confbridge_event_type_to_string(confbridge_welcome_type());
+
+               json_bridge = bridge_to_json(obj->bridge);
+               json_object = pack_bridge_and_channels(json_bridge, json_channels, msg);
+               if (!json_object) {
+                       ast_log(LOG_ERROR, "Unable to convert ConfbridgeWelcome message to json\n");
+                       return;
+               }
+               ast_json_string_set(ast_json_object_get(json_object, "type"), welcome_msg_name);
+
+               send_message(welcome_msg_name, conference->name, json_object, chan);
+               ast_json_unref(json_object);
+       }
+}
+
 static void send_conf_stasis(struct confbridge_conference *conference, struct ast_channel *chan,
        struct stasis_message_type *type, struct ast_json *extras, int channel_topic)
 {
@@ -573,6 +884,10 @@ static void send_conf_stasis(struct confbridge_conference *conference, struct as
                return;
        }
 
+       if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_ENABLE_EVENTS)) {
+               send_event_to_participants(conference, chan, msg);
+       }
+
        if (channel_topic) {
                stasis_publish(ast_channel_topic(chan), msg);
        } else {
index 873831911c19a1bb52724845394b8626bb1226dc..9e56b3665bcc87cf55d84a7ea08d6acb8489c00d 100644 (file)
                                <configOption name="admin">
                                        <synopsis>Sets if the user is an admin or not</synopsis>
                                </configOption>
+                               <configOption name="send_events" default="no">
+                                       <synopsis>Sets if events are send to the user</synopsis>
+                                       <description><para>If events are enabled for this bridge and this option is
+                                       set, users will receive events like join, leave, talking, etc. via text
+                                       messages.  For users accessing the bridge via chan_pjsip, this means
+                                       in-dialog MESSAGE messages.  This is most useful for WebRTC participants
+                                       where the browser application can use the messages to alter the user
+                                       interface.</para></description>
+                               </configOption>
+                               <configOption name="echo_events" default="yes">
+                                       <synopsis>Sets if events are echoed back to the user that
+                                       triggered them</synopsis>
+                                       <description><para>If events are enabled for this user and this option
+                                       is set, the user will receive events they trigger, talking, mute, etc.
+                                       If not set, they will not receive their own events.
+                                       </para></description>
+                               </configOption>
                                <configOption name="marked">
                                        <synopsis>Sets if this is a marked user or not</synopsis>
                                </configOption>
                                                </enumlist>
                                        </description>
                                </configOption>
+                               <configOption name="enable_events" default="no">
+                                       <synopsis>Enables events for this bridge</synopsis>
+                                       <description><para>
+                                               If enabled, recipients who joined the bridge via a channel driver
+                                               that supports Enhanced Messaging (currently only chan_pjsip) will
+                                               receive in-dialog messages containing a JSON body describing the
+                                               event.  The Content-Type header will be
+                                               <literal>text/x-ast-confbridge-event</literal>.
+                                               This feature must also be enabled in user profiles.</para>
+                                       </description>
+                               </configOption>
                                <configOption name="template">
                                        <synopsis>When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile</synopsis>
                                </configOption>
@@ -1478,6 +1506,12 @@ static char *handle_cli_confbridge_show_user_profile(struct ast_cli_entry *e, in
        ast_cli(a->fd,"Admin:                   %s\n",
                u_profile.flags & USER_OPT_ADMIN ?
                "true" : "false");
+       ast_cli(a->fd,"Send Events:             %s\n",
+               u_profile.flags & USER_OPT_SEND_EVENTS ?
+               "true" : "false");
+       ast_cli(a->fd,"Echo Events:             %s\n",
+               u_profile.flags & USER_OPT_ECHO_EVENTS ?
+               "true" : "false");
        ast_cli(a->fd,"Marked User:             %s\n",
                u_profile.flags & USER_OPT_MARKEDUSER ?
                "true" : "false");
@@ -1718,6 +1752,10 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e,
                break;
        }
 
+       ast_cli(a->fd,"Enable Events:             %s\n",
+               b_profile.flags & BRIDGE_OPT_ENABLE_EVENTS ?
+               "yes" : "no");
+
        ast_cli(a->fd,"sound_only_person:    %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds));
        ast_cli(a->fd,"sound_only_one:       %s\n", conf_get_sound(CONF_SOUND_ONLY_ONE, b_profile.sounds));
        ast_cli(a->fd,"sound_has_joined:     %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds));
@@ -2265,6 +2303,8 @@ int conf_load_config(void)
        /* User options */
        aco_option_register(&cfg_info, "type", ACO_EXACT, user_types, NULL, OPT_NOOP_T, 0, 0);
        aco_option_register(&cfg_info, "admin", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ADMIN);
+       aco_option_register(&cfg_info, "send_events", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_SEND_EVENTS);
+       aco_option_register(&cfg_info, "echo_events", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ECHO_EVENTS);
        aco_option_register(&cfg_info, "marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MARKEDUSER);
        aco_option_register(&cfg_info, "startmuted", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_STARTMUTED);
        aco_option_register(&cfg_info, "music_on_hold_when_empty", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MUSICONHOLD);
@@ -2288,6 +2328,7 @@ int conf_load_config(void)
        aco_option_register(&cfg_info, "dsp_talking_threshold", ACO_EXACT, user_types, __stringify(DEFAULT_TALKING_THRESHOLD), OPT_UINT_T, 0, FLDSET(struct user_profile, talking_threshold));
        aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_JITTERBUFFER);
        aco_option_register(&cfg_info, "timeout", ACO_EXACT, user_types, "0", OPT_UINT_T, 0, FLDSET(struct user_profile, timeout));
+
        /* This option should only be used with the CONFBRIDGE dialplan function */
        aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0);
 
@@ -2313,6 +2354,7 @@ int conf_load_config(void)
        aco_option_register(&cfg_info, "video_update_discard", ACO_EXACT, bridge_types, "2000", OPT_UINT_T, 0, FLDSET(struct bridge_profile, video_update_discard));
        aco_option_register(&cfg_info, "remb_send_interval", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, remb_send_interval));
        aco_option_register_custom(&cfg_info, "remb_behavior", ACO_EXACT, bridge_types, "average", remb_behavior_handler, 0);
+       aco_option_register(&cfg_info, "enable_events", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_ENABLE_EVENTS);
        /* This option should only be used with the CONFBRIDGE dialplan function */
        aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0);
 
index 0f3c7fb17e9b45b2a16b7bcb6c3cf4ed1fd4fb46..823e69aa32c99c6a0e7c3211a7bad6f4f7a7e39b 100644 (file)
 static struct stasis_message_router *bridge_state_router;
 static struct stasis_message_router *channel_state_router;
 
+STASIS_MESSAGE_TYPE_DEFN(confbridge_start_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_end_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_join_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_leave_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_start_record_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_stop_record_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_mute_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_unmute_type);
+STASIS_MESSAGE_TYPE_DEFN(confbridge_talking_type);
+/*
+ * The welcome message is defined here but is only sent
+ * to participants and only when events are enabled.
+ * At the current time, no actual stasis or AMI events
+ * are generated for this type.
+ */
+STASIS_MESSAGE_TYPE_DEFN(confbridge_welcome_type);
+
+const char *confbridge_event_type_to_string(struct stasis_message_type *event_type)
+{
+       if (event_type == confbridge_start_type()) {
+               return "ConfbridgeStart";
+       } else if (event_type == confbridge_end_type()) {
+               return "ConfbridgeEnd";
+       } else if (event_type == confbridge_join_type()) {
+               return "ConfbridgeJoin";
+       } else if (event_type == confbridge_leave_type()) {
+               return "ConfbridgeLeave";
+       } else if (event_type == confbridge_start_record_type()) {
+               return "ConfbridgeRecord";
+       } else if (event_type == confbridge_stop_record_type()) {
+               return "ConfbridgeStopRecord";
+       } else if (event_type == confbridge_mute_type()) {
+               return "ConfbridgeMute";
+       } else if (event_type == confbridge_unmute_type()) {
+               return "ConfbridgeUnmute";
+       } else if (event_type == confbridge_talking_type()) {
+               return "ConfbridgeTalking";
+       } else if (event_type == confbridge_welcome_type()) {
+               return "ConfbridgeWelcome";
+       } else {
+               return "unknown";
+       }
+}
+
 static void confbridge_publish_manager_event(
        struct stasis_message *message,
-       const char *event,
        struct ast_str *extra_text)
 {
        struct ast_bridge_blob *blob = stasis_message_data(message);
+       const char *event = confbridge_event_type_to_string(stasis_message_type(message));
        const char *conference_name;
        RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
        RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
@@ -291,13 +335,13 @@ static int get_muted_header(struct ast_str **extra_text, struct stasis_message *
 static void confbridge_start_cb(void *data, struct stasis_subscription *sub,
        struct stasis_message *message)
 {
-       confbridge_publish_manager_event(message, "ConfbridgeStart", NULL);
+       confbridge_publish_manager_event(message, NULL);
 }
 
 static void confbridge_end_cb(void *data, struct stasis_subscription *sub,
        struct stasis_message *message)
 {
-       confbridge_publish_manager_event(message, "ConfbridgeEnd", NULL);
+       confbridge_publish_manager_event(message, NULL);
 }
 
 static void confbridge_leave_cb(void *data, struct stasis_subscription *sub,
@@ -306,7 +350,7 @@ static void confbridge_leave_cb(void *data, struct stasis_subscription *sub,
        struct ast_str *extra_text = NULL;
 
        if (!get_admin_header(&extra_text, message)) {
-               confbridge_publish_manager_event(message, "ConfbridgeLeave", extra_text);
+               confbridge_publish_manager_event(message, extra_text);
        }
        ast_free(extra_text);
 }
@@ -318,7 +362,7 @@ static void confbridge_join_cb(void *data, struct stasis_subscription *sub,
 
        if (!get_admin_header(&extra_text, message)
                && !get_muted_header(&extra_text, message)) {
-               confbridge_publish_manager_event(message, "ConfbridgeJoin", extra_text);
+               confbridge_publish_manager_event(message, extra_text);
        }
        ast_free(extra_text);
 }
@@ -326,13 +370,13 @@ static void confbridge_join_cb(void *data, struct stasis_subscription *sub,
 static void confbridge_start_record_cb(void *data, struct stasis_subscription *sub,
        struct stasis_message *message)
 {
-       confbridge_publish_manager_event(message, "ConfbridgeRecord", NULL);
+       confbridge_publish_manager_event(message, NULL);
 }
 
 static void confbridge_stop_record_cb(void *data, struct stasis_subscription *sub,
        struct stasis_message *message)
 {
-       confbridge_publish_manager_event(message, "ConfbridgeStopRecord", NULL);
+       confbridge_publish_manager_event(message, NULL);
 }
 
 static void confbridge_mute_cb(void *data, struct stasis_subscription *sub,
@@ -341,7 +385,7 @@ static void confbridge_mute_cb(void *data, struct stasis_subscription *sub,
        struct ast_str *extra_text = NULL;
 
        if (!get_admin_header(&extra_text, message)) {
-               confbridge_publish_manager_event(message, "ConfbridgeMute", extra_text);
+               confbridge_publish_manager_event(message, extra_text);
        }
        ast_free(extra_text);
 }
@@ -352,7 +396,7 @@ static void confbridge_unmute_cb(void *data, struct stasis_subscription *sub,
        struct ast_str *extra_text = NULL;
 
        if (!get_admin_header(&extra_text, message)) {
-               confbridge_publish_manager_event(message, "ConfbridgeUnmute", extra_text);
+               confbridge_publish_manager_event(message, extra_text);
        }
        ast_free(extra_text);
 }
@@ -373,20 +417,10 @@ static void confbridge_talking_cb(void *data, struct stasis_subscription *sub,
        }
 
        if (!get_admin_header(&extra_text, message)) {
-               confbridge_publish_manager_event(message, "ConfbridgeTalking", extra_text);
+               confbridge_publish_manager_event(message, extra_text);
        }
 }
 
-STASIS_MESSAGE_TYPE_DEFN(confbridge_start_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_end_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_join_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_leave_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_start_record_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_stop_record_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_mute_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_unmute_type);
-STASIS_MESSAGE_TYPE_DEFN(confbridge_talking_type);
-
 void manager_confbridge_shutdown(void) {
        STASIS_MESSAGE_TYPE_CLEANUP(confbridge_start_type);
        STASIS_MESSAGE_TYPE_CLEANUP(confbridge_end_type);
@@ -397,6 +431,7 @@ void manager_confbridge_shutdown(void) {
        STASIS_MESSAGE_TYPE_CLEANUP(confbridge_mute_type);
        STASIS_MESSAGE_TYPE_CLEANUP(confbridge_unmute_type);
        STASIS_MESSAGE_TYPE_CLEANUP(confbridge_talking_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(confbridge_welcome_type);
 
        if (bridge_state_router) {
                stasis_message_router_unsubscribe(bridge_state_router);
@@ -420,6 +455,7 @@ int manager_confbridge_init(void)
        STASIS_MESSAGE_TYPE_INIT(confbridge_mute_type);
        STASIS_MESSAGE_TYPE_INIT(confbridge_unmute_type);
        STASIS_MESSAGE_TYPE_INIT(confbridge_talking_type);
+       STASIS_MESSAGE_TYPE_INIT(confbridge_welcome_type);
 
        bridge_state_router = stasis_message_router_create(
                ast_bridge_topic_all_cached());
@@ -564,5 +600,7 @@ int manager_confbridge_init(void)
                return -1;
        }
 
+       /* FYI: confbridge_welcome_type is never routed */
+
        return 0;
 }
index f9187e06c1c5bc72945952c9eba6762523a65e20..8329335338d8ee18fa83dc311259d663446a69dc 100644 (file)
@@ -65,6 +65,8 @@ enum user_profile_flags {
        USER_OPT_ANNOUNCEUSERCOUNTALL = (1 << 14), /*!< Sets if the number of users should be announced to everyone. */
        USER_OPT_JITTERBUFFER =  (1 << 15), /*!< Places a jitterbuffer on the user. */
        USER_OPT_ANNOUNCE_JOIN_LEAVE_REVIEW = (1 << 16), /*!< modifies ANNOUNCE_JOIN_LEAVE - user reviews the recording before continuing */
+       USER_OPT_SEND_EVENTS = (1 << 17), /*!< Send text message events to users */
+       USER_OPT_ECHO_EVENTS = (1 << 18), /*!< Send events only to the admin(s) */
 };
 
 enum bridge_profile_flags {
@@ -79,6 +81,7 @@ enum bridge_profile_flags {
        BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE = (1 << 8), /*!< The average of all REMB reports is sent to the sender */
        BRIDGE_OPT_REMB_BEHAVIOR_LOWEST = (1 << 9), /*!< The lowest estimated maximum bitrate is sent to the sender */
        BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST = (1 << 10), /*!< The highest estimated maximum bitrate is sent to the sender */
+       BRIDGE_OPT_ENABLE_EVENTS = (1 << 11), /*!< Enable sending events to participants */
 };
 
 enum conf_menu_action_id {
@@ -625,6 +628,26 @@ struct stasis_message_type *confbridge_unmute_type(void);
  */
 struct stasis_message_type *confbridge_talking_type(void);
 
+/*!
+ * \since 15.5
+ * \brief get the confbridge welcome stasis message type
+ *
+ * \retval stasis message type for confbridge welcome messages if it's available
+ * \retval NULL if it isn't
+ */
+struct stasis_message_type *confbridge_welcome_type(void);
+
+/*!
+ * \since 15.5
+ * \brief Get the string representation of a confbridge stasis message type
+ *
+ * \param event_type The confbridge event type such as 'confbridge_welcome_type()'
+ *
+ * \retval The string representation of the message type
+ * \retval "unknown" if not found
+ */
+const char *confbridge_event_type_to_string(struct stasis_message_type *event_type);
+
 /*!
  * \since 12.0
  * \brief register stasis message routers to handle manager events for confbridge messages
index 8b276cdb8013b2b2c55696ed7646df8a8ddc19ec..a214f345ba65a9d7370d3048d036b94c01e52332 100644 (file)
 [default_user]
 type=user
 ;admin=yes     ; Sets if the user is an admin or not. Off by default.
+
+;send_events=no  ; If events are enabled for this bridge and this option is
+                 ; set, users will receive events like join, leave, talking,
+                 ; etc. via text messages.  For users accessing the bridge
+                 ; via chan_pjsip, this means in-dialog MESSAGE messages.
+                 ; This is most useful for WebRTC participants where the
+                 ; browser application can use the messages to alter the user
+                 ; interface.
+;echo_events=yes ; If events are enabled for this user and this option is set,
+                 ; the user will receive events they trigger, talking, mute, etc.
+                 ; If not set, they will not receive their own events.
+
 ;marked=yes    ; Sets if this is a marked user or not. Off by default.
 ;startmuted=yes; Sets if all users should start out muted. Off by default
 ;music_on_hold_when_empty=yes  ; Sets whether MOH should be played when only
@@ -244,6 +256,13 @@ type=bridge
                            ; set to "lowest" the lowest maximum bitrate is forwarded to the sender. If set to "highest"
                            ; the highest maximum bitrate is forwarded to the sender. This defaults to "average".
 
+;enable_events=no          ; If enabled, recipients who joined the bridge via a channel driver
+                           ; that supports Enhanced Messaging (currently only chan_pjsip) will
+                           ; receive in-dialog messages containing a JSON body describing the
+                           ; event.  The Content-Type header will be
+                           ; "text/x-ast-confbridge-event".
+                           ; This feature must also be enabled in user profiles.
+
 ; All sounds in the conference are customizable using the bridge profile options below.
 ; Simply state the option followed by the filename or full path of the filename after
 ; the option.  Example: sound_had_joined=conf-hasjoin  This will play the conf-hasjoin
index 0cc2de9d991985e3afe86e1bd4ebcf030042c5b8..c9c0f17eca10552c9caf13dd62356189a2a13b98 100644 (file)
@@ -1119,10 +1119,24 @@ static void add_msid_to_stream(struct ast_sip_session *session,
        }
 
        if (ast_strlen_zero(session_media->label)) {
-               ast_uuid_generate_str(session_media->label, sizeof(session_media->label));
+               /*
+                * If this stream has already been assigned a label, use it.
+                * This will ensure that a confbridge participant is known by
+                * the same label by all other participants.
+                */
+               const char *stream_label = ast_stream_get_metadata(stream, "MSID:LABEL");
+
+               if (!ast_strlen_zero(stream_label)) {
+                       ast_copy_string(session_media->label, stream_label, sizeof(session_media->label));
+               } else {
+                       ast_uuid_generate_str(session_media->label, sizeof(session_media->label));
+                       ast_stream_set_metadata(stream, "MSID:LABEL", session_media->label);
+               }
        }
 
        snprintf(msid, sizeof(msid), "%s %s", session_media->mslabel, session_media->label);
+       ast_debug(3, "Stream msid: %p %s %s\n", stream,
+               ast_codec_media_type2str(ast_stream_get_type(stream)), msid);
        attr = pjmedia_sdp_attr_create(pool, "msid", pj_cstr(&stmp, msid));
        pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
 }