]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res/ari/resource_bridges: Add the ability to manipulate the video source 28/4428/2
authorMatt Jordan <mjordan@digium.com>
Tue, 8 Nov 2016 16:11:41 +0000 (10:11 -0600)
committerMatt Jordan <mjordan@digium.com>
Mon, 14 Nov 2016 21:56:45 +0000 (15:56 -0600)
In multi-party bridges, Asterisk currently supports two video modes:
 * Follow the talker, in which the speaker with the most energy is shown
   to all participants but the speaker, and the speaker sees the
   previous video source
 * Explicitly set video sources, in which all participants see a locked
   video source

Prior to this patch, ARI had no ability to manipulate the video source.
This isn't important for two-party bridges, in which Asterisk merely
relays the video between the participants. However, in a multi-party
bridge, it can be advantageous to allow an external application to
manipulate the video source.

This patch provides two new routes to accomplish this:
(1) setVideoSource: POST /bridges/{bridgeId}/videoSource/{channelId}
    Sets a video source to an explicit channel
(2) clearVideoSource: DELETE /bridges/{bridgeId}/videoSource
    Removes any explicit video source, and sets the video mode to talk
    detection

ASTERISK-26595 #close

Change-Id: I98e455d5bffc08ea5e8d6b84ccaf063c714e6621

15 files changed:
CHANGES
doc/appdocsxml.xslt
include/asterisk/bridge.h
include/asterisk/stasis_bridges.h
main/bridge.c
main/manager_bridges.c
main/stasis_bridges.c
res/ari/ari_model_validators.c
res/ari/ari_model_validators.h
res/ari/resource_bridges.c
res/ari/resource_bridges.h
res/res_ari_bridges.c
res/stasis/app.c
rest-api/api-docs/bridges.json
rest-api/api-docs/events.json

diff --git a/CHANGES b/CHANGES
index 91e8142911d68139ef192a5d1ba3111d5150aeee..f201df3254cc4ae77102ab94a19ac4168d68bf5d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
 --- Functionality changes from Asterisk 14.1.0 to Asterisk 14.2.0 ----------
 ------------------------------------------------------------------------------
 
+AMI
+------------------
+ * Events that reference a bridge may now contain two new optional fields:
+   - 'BridgeVideoSourceMode': the video source mode for the bridge.
+     Can be one of 'none', 'talker', or 'single'.
+   - 'BridgeVideoSource': the unique ID of the channel that is the video
+     source in this bridge, if one exists.
+
+ * A new event, BridgeVideoSourceUpdate, has been added with a class
+   authorization of CALL. The event is raised when the video source changes
+   in a multi-party mixing bridge.
+
+ARI
+------------------
+ * The bridges resource now exposes two new operations:
+   - POST /bridges/{bridgeId}/videoSource/{channelId}: Set a video source in a
+     multi-party mixing bridge
+   - DELETE /bridges/{bridgeId}/videoSource: Remove the set video source,
+     reverting to talk detection for the video source
+
+ * The bridge model in any returned response or event now contains the following
+   optional fields:
+   - video_mode: the video source mode for the bridge. Can be one of 'none',
+     'talker', or 'single'.
+   - video_source_id: the unique ID of the channel that is the video source
+     in this bridge, if one exists.
+
+ * A new event, BridgeVideoSourceChanged, has been added for bridges.
+   Applications subscribed to a bridge will receive this event when the source
+   of video changes in a mixing bridge.
+
 res_pjsip
 ------------------
  * Automatic dual stack support is now implemented. Depending on DNS resolution
index f067decaef29141eb9cccb93c2d996fcbb81122d..511011ae8c5d0c4a94391398e3294a8a2ff3ed27 100644 (file)
             </xsl:attribute>
             <para>Number of channels in the bridge</para>
         </xsl:element>
+        <xsl:element name="parameter">
+            <xsl:attribute name="name">
+                <xsl:value-of select="concat(@prefix, 'BridgeVideoSourceMode')" />
+            </xsl:attribute>
+            <enumlist>
+                <enum name="none"/>
+                <enum name="talker"/>
+                <enum name="single"/>
+            </enumlist>
+            <para>The video source mode for the bridge.</para>
+        </xsl:element>
+        <xsl:element name="parameter">
+            <xsl:attribute name="required">
+                false
+            </xsl:attribute>
+            <xsl:attribute name="name">
+                <xsl:value-of select="concat(@prefix, 'BridgeVideoSource')" />
+            </xsl:attribute>
+            <para>If there is a video source for the bridge, the unique ID of the channel that is the video source.</para>
+        </xsl:element>
     </xsl:template>
 </xsl:stylesheet>
index acea2f01fbbef55d089544847da5f5610e767e3c..61cecbdd64466798f45136b919b5c9891a4dc18a 100644 (file)
@@ -903,6 +903,15 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
  */
 void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
 
+/*!
+ * \brief Converts an enum representation of a bridge video mode to string
+ *
+ * \param video_mode The video mode
+ *
+ * \retval A string representation of \c video_mode
+ */
+const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode);
+
 enum ast_transfer_result {
        /*! The transfer completed successfully */
        AST_BRIDGE_TRANSFER_SUCCESS,
index d549e462022a629c6f4b151afa2ef6f7979eb77b..05d356cc2c144a7d4cbb6d07f442dc100e68b8df 100644 (file)
@@ -58,6 +58,10 @@ struct ast_bridge_snapshot {
        unsigned int num_channels;
        /*! Number of active channels in the bridge. */
        unsigned int num_active;
+       /*! The video mode of the bridge */
+       enum ast_bridge_video_mode_type video_mode;
+       /*! Unique ID of the channel providing video, if one exists */
+       AST_STRING_FIELD_EXTENDED(video_source_id);
 };
 
 /*!
index ebf3f6c6cd138344295cc1d93a20aa5f5c9b2376..ce1c97aaf2036008dbf4e68b74682c972deb3ea1 100644 (file)
@@ -3776,8 +3776,7 @@ void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_
                bridge->name, bridge->uniqueid,
                ast_channel_name(video_src_chan),
                ast_channel_uniqueid(video_src_chan));
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %u\r\nVideo Channel: %s",
-               bridge->softmix.video_mode.mode, ast_channel_name(video_src_chan));
+       ast_bridge_publish_state(bridge);
        ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
        ast_bridge_unlock(bridge);
 }
@@ -3787,8 +3786,6 @@ void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
        ast_bridge_lock(bridge);
        cleanup_video_mode(bridge);
        bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %u",
-               bridge->softmix.video_mode.mode);
        ast_bridge_unlock(bridge);
 }
 
@@ -3820,7 +3817,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
                        bridge->name, bridge->uniqueid,
                        ast_channel_name(data->chan_vsrc),
                        ast_channel_uniqueid(data->chan_vsrc));
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_bridge_publish_state(bridge);
                ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
        } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
                ast_indicate(chan, AST_CONTROL_VIDUPDATE);
@@ -3831,7 +3828,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
                        bridge->name, bridge->uniqueid,
                        ast_channel_name(data->chan_vsrc),
                        ast_channel_uniqueid(data->chan_vsrc));
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_bridge_publish_state(bridge);
                ast_indicate(chan, AST_CONTROL_VIDUPDATE);
        } else if (!data->chan_old_vsrc && is_keyframe) {
                data->chan_old_vsrc = ast_channel_ref(chan);
@@ -3922,6 +3919,19 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *
        ast_bridge_unlock(bridge);
 }
 
+const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode)
+{
+       switch (video_mode) {
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               return "talker";
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               return "single";
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+       default:
+               return "none";
+       }
+}
+
 static int channel_hash(const void *obj, int flags)
 {
        const struct ast_channel *chan = obj;
index b6aaa550e63a5df5bd47c0363813183e189a6247..eac7bf230333dd5b6ef3a0bd48d02c55c00254fe 100644 (file)
@@ -93,6 +93,21 @@ static struct stasis_message_router *bridge_state_router;
                        </see-also>
                </managerEventInstance>
        </managerEvent>
+       <managerEvent language="en_US" name="BridgeVideoSourceUpdate">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when the channel that is the source of video in a bridge changes.</synopsis>
+                       <syntax>
+                               <bridge_snapshot/>
+                               <parameter name="BridgePreviousVideoSource">
+                                       <para>The unique ID of the channel that was the video source.</para>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">BridgeCreate</ref>
+                               <ref type="managerEvent">BridgeDestroy</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
        <manager name="BridgeList" language="en_US">
                <synopsis>
                        Get a list of bridges in the system.
@@ -224,18 +239,30 @@ struct ast_str *ast_manager_build_bridge_state_string_prefix(
                "%sBridgeTechnology: %s\r\n"
                "%sBridgeCreator: %s\r\n"
                "%sBridgeName: %s\r\n"
-               "%sBridgeNumChannels: %u\r\n",
+               "%sBridgeNumChannels: %u\r\n"
+               "%sBridgeVideoSourceMode: %s\r\n",
                prefix, snapshot->uniqueid,
                prefix, snapshot->subclass,
                prefix, snapshot->technology,
                prefix, ast_strlen_zero(snapshot->creator) ? "<unknown>": snapshot->creator,
                prefix, ast_strlen_zero(snapshot->name) ? "<unknown>": snapshot->name,
-               prefix, snapshot->num_channels);
+               prefix, snapshot->num_channels,
+               prefix, ast_bridge_video_mode_to_string(snapshot->video_mode));
        if (!res) {
                ast_free(out);
                return NULL;
        }
 
+       if (snapshot->video_mode != AST_BRIDGE_VIDEO_MODE_NONE
+               && !ast_strlen_zero(snapshot->video_source_id)) {
+               res = ast_str_append(&out, 0, "%sBridgeVideoSource: %s\r\n",
+                       prefix, snapshot->video_source_id);
+               if (!res) {
+                       ast_free(out);
+                       return NULL;
+               }
+       }
+
        return out;
 }
 
@@ -263,6 +290,25 @@ static struct ast_manager_event_blob *bridge_create(
                EVENT_FLAG_CALL, "BridgeCreate", NO_EXTRA_FIELDS);
 }
 
+/* \brief Handle video source updates */
+static struct ast_manager_event_blob *bridge_video_update(
+       struct ast_bridge_snapshot *old_snapshot,
+       struct ast_bridge_snapshot *new_snapshot)
+{
+       if (!new_snapshot || !old_snapshot) {
+               return NULL;
+       }
+
+       if (!strcmp(old_snapshot->video_source_id, new_snapshot->video_source_id)) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(
+               EVENT_FLAG_CALL, "BridgeVideoSourceUpdate",
+               "BridgePreviousVideoSource: %s\r\n",
+               old_snapshot->video_source_id);
+}
+
 /*! \brief Handle bridge destruction */
 static struct ast_manager_event_blob *bridge_destroy(
        struct ast_bridge_snapshot *old_snapshot,
@@ -276,9 +322,9 @@ static struct ast_manager_event_blob *bridge_destroy(
                EVENT_FLAG_CALL, "BridgeDestroy", NO_EXTRA_FIELDS);
 }
 
-
 bridge_snapshot_monitor bridge_monitors[] = {
        bridge_create,
+       bridge_video_update,
        bridge_destroy,
 };
 
index a5f29c74eff481b77a001816a68cb025570b1a1a..1373df5fb0a8debbd2e462888e95a938053c02b8 100644 (file)
@@ -244,7 +244,13 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge
 
        snapshot = ao2_alloc_options(sizeof(*snapshot), bridge_snapshot_dtor,
                AO2_ALLOC_OPT_LOCK_NOLOCK);
-       if (!snapshot || ast_string_field_init(snapshot, 128)) {
+       if (!snapshot) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(snapshot, 128)
+               || ast_string_field_init_extended(snapshot, video_source_id)) {
+               ao2_ref(snapshot, -1);
                return NULL;
        }
 
@@ -270,6 +276,16 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge
        snapshot->capabilities = bridge->technology->capabilities;
        snapshot->num_channels = bridge->num_channels;
        snapshot->num_active = bridge->num_active;
+       snapshot->video_mode = bridge->softmix.video_mode.mode;
+       if (snapshot->video_mode == AST_BRIDGE_VIDEO_MODE_SINGLE_SRC
+               && bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc) {
+               ast_string_field_set(snapshot, video_source_id,
+                       ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc));
+       } else if (snapshot->video_mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC
+               && bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc) {
+               ast_string_field_set(snapshot, video_source_id,
+                       ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc));
+       }
 
        ao2_ref(snapshot, +1);
        return snapshot;
@@ -592,18 +608,25 @@ struct ast_json *ast_bridge_snapshot_to_json(
                return NULL;
        }
 
-       json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: o}",
+       json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: o, s: s}",
                "id", snapshot->uniqueid,
                "technology", snapshot->technology,
                "bridge_type", capability2str(snapshot->capabilities),
                "bridge_class", snapshot->subclass,
                "creator", snapshot->creator,
                "name", snapshot->name,
-               "channels", json_channels);
+               "channels", json_channels,
+               "video_mode", ast_bridge_video_mode_to_string(snapshot->video_mode));
        if (!json_bridge) {
                return NULL;
        }
 
+       if (snapshot->video_mode != AST_BRIDGE_VIDEO_MODE_NONE
+               && !ast_strlen_zero(snapshot->video_source_id)) {
+               ast_json_object_set(json_bridge, "video_source_id",
+                       ast_json_string_create(snapshot->video_source_id));
+       }
+
        return ast_json_ref(json_bridge);
 }
 
index 633a94c1b0f1d1fa3ec4960c9ce7ff09e2a2cf75..1457376ab10d649737b4e59d97a761c244532411 100644 (file)
@@ -1360,6 +1360,24 @@ int ast_ari_validate_bridge(struct ast_json *json)
                                res = 0;
                        }
                } else
+               if (strcmp("video_mode", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Bridge field video_mode failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("video_source_id", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Bridge field video_source_id failed validation\n");
+                               res = 0;
+                       }
+               } else
                {
                        ast_log(LOG_ERROR,
                                "ARI Bridge has undocumented field %s\n",
@@ -2715,6 +2733,103 @@ ari_validator ast_ari_validate_bridge_merged_fn(void)
        return ast_ari_validate_bridge_merged;
 }
 
+int ast_ari_validate_bridge_video_source_changed(struct ast_json *json)
+{
+       int res = 1;
+       struct ast_json_iter *iter;
+       int has_type = 0;
+       int has_application = 0;
+       int has_bridge = 0;
+
+       for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+               if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field asterisk_id failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_type = 1;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field type failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_application = 1;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field application failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_date(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field timestamp failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_bridge = 1;
+                       prop_is_valid = ast_ari_validate_bridge(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field bridge failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("old_video_source_id", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field old_video_source_id failed validation\n");
+                               res = 0;
+                       }
+               } else
+               {
+                       ast_log(LOG_ERROR,
+                               "ARI BridgeVideoSourceChanged has undocumented field %s\n",
+                               ast_json_object_iter_key(iter));
+                       res = 0;
+               }
+       }
+
+       if (!has_type) {
+               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field type\n");
+               res = 0;
+       }
+
+       if (!has_application) {
+               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field application\n");
+               res = 0;
+       }
+
+       if (!has_bridge) {
+               ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field bridge\n");
+               res = 0;
+       }
+
+       return res;
+}
+
+ari_validator ast_ari_validate_bridge_video_source_changed_fn(void)
+{
+       return ast_ari_validate_bridge_video_source_changed;
+}
+
 int ast_ari_validate_channel_caller_id(struct ast_json *json)
 {
        int res = 1;
@@ -4921,6 +5036,9 @@ int ast_ari_validate_event(struct ast_json *json)
        if (strcmp("BridgeMerged", discriminator) == 0) {
                return ast_ari_validate_bridge_merged(json);
        } else
+       if (strcmp("BridgeVideoSourceChanged", discriminator) == 0) {
+               return ast_ari_validate_bridge_video_source_changed(json);
+       } else
        if (strcmp("ChannelCallerId", discriminator) == 0) {
                return ast_ari_validate_channel_caller_id(json);
        } else
@@ -5116,6 +5234,9 @@ int ast_ari_validate_message(struct ast_json *json)
        if (strcmp("BridgeMerged", discriminator) == 0) {
                return ast_ari_validate_bridge_merged(json);
        } else
+       if (strcmp("BridgeVideoSourceChanged", discriminator) == 0) {
+               return ast_ari_validate_bridge_video_source_changed(json);
+       } else
        if (strcmp("ChannelCallerId", discriminator) == 0) {
                return ast_ari_validate_channel_caller_id(json);
        } else
index 0b08ce85e5a9189f72a91721ac67493789732cb4..a62bdf22cd7e2249b5bbf0fce092528c5419fa96 100644 (file)
@@ -716,6 +716,24 @@ int ast_ari_validate_bridge_merged(struct ast_json *json);
  */
 ari_validator ast_ari_validate_bridge_merged_fn(void);
 
+/*!
+ * \brief Validator for BridgeVideoSourceChanged.
+ *
+ * Notification that the source of video in a bridge has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_bridge_video_source_changed(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_bridge_video_source_changed().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_bridge_video_source_changed_fn(void);
+
 /*!
  * \brief Validator for ChannelCallerId.
  *
@@ -1452,6 +1470,8 @@ ari_validator ast_ari_validate_application_fn(void);
  * - id: string (required)
  * - name: string (required)
  * - technology: string (required)
+ * - video_mode: string
+ * - video_source_id: string
  * LiveRecording
  * - cause: string
  * - duration: int
@@ -1543,6 +1563,13 @@ ari_validator ast_ari_validate_application_fn(void);
  * - timestamp: Date
  * - bridge: Bridge (required)
  * - bridge_from: Bridge (required)
+ * BridgeVideoSourceChanged
+ * - asterisk_id: string
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - bridge: Bridge (required)
+ * - old_video_source_id: string
  * ChannelCallerId
  * - asterisk_id: string
  * - type: string (required)
index 28c3e43605217b3df433feacae05633ef6ac2746..74fcfdb8fc7c8c29996e424ac4315254d8b57073 100644 (file)
@@ -37,6 +37,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_app.h"
+#include "asterisk/stasis_app_impl.h"
 #include "asterisk/stasis_app_playback.h"
 #include "asterisk/stasis_app_recording.h"
 #include "asterisk/stasis_channels.h"
@@ -1005,3 +1006,68 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers,
        ast_ari_response_ok(response,
                ast_bridge_snapshot_to_json(snapshot, stasis_app_get_sanitizer()));
 }
+
+static int bridge_set_video_source_cb(struct stasis_app_control *control,
+       struct ast_channel *chan, void *data)
+{
+       struct ast_bridge *bridge = data;
+
+       ast_bridge_lock(bridge);
+       ast_bridge_set_single_src_video_mode(bridge, chan);
+       ast_bridge_unlock(bridge);
+
+       return 0;
+}
+
+void ast_ari_bridges_set_video_source(struct ast_variable *headers,
+       struct ast_ari_bridges_set_video_source_args *args, struct ast_ari_response *response)
+{
+       struct ast_bridge *bridge;
+       struct stasis_app_control *control;
+
+       bridge = find_bridge(response, args->bridge_id);
+       if (!bridge) {
+               return;
+       }
+
+       control = find_channel_control(response, args->channel_id);
+       if (!control) {
+               ao2_ref(bridge, -1);
+               return;
+       }
+
+       if (stasis_app_get_bridge(control) != bridge) {
+               ast_ari_response_error(response, 422,
+                       "Unprocessable Entity",
+                       "Channel not in this bridge");
+               ao2_ref(bridge, -1);
+               ao2_ref(control, -1);
+               return;
+       }
+
+       stasis_app_send_command(control, bridge_set_video_source_cb,
+               ao2_bump(bridge), __ao2_cleanup);
+
+       ao2_ref(bridge, -1);
+       ao2_ref(control, -1);
+
+       ast_ari_response_no_content(response);
+}
+
+void ast_ari_bridges_clear_video_source(struct ast_variable *headers,
+       struct ast_ari_bridges_clear_video_source_args *args, struct ast_ari_response *response)
+{
+       struct ast_bridge *bridge;
+
+       bridge = find_bridge(response, args->bridge_id);
+       if (!bridge) {
+               return;
+       }
+
+       ast_bridge_lock(bridge);
+       ast_bridge_set_talker_src_video_mode(bridge);
+       ast_bridge_unlock(bridge);
+
+       ao2_ref(bridge, -1);
+       ast_ari_response_no_content(response);
+}
index 17a3b8365f0974631e7ea38ba372c71594c5ffd8..e75d8e028731526af9f58bbb5daa0c12ba4a822e 100644 (file)
@@ -200,6 +200,34 @@ int ast_ari_bridges_remove_channel_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_bridges_remove_channel(struct ast_variable *headers, struct ast_ari_bridges_remove_channel_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_set_video_source() */
+struct ast_ari_bridges_set_video_source_args {
+       /*! Bridge's id */
+       const char *bridge_id;
+       /*! Channel's id */
+       const char *channel_id;
+};
+/*!
+ * \brief Set a channel as the video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_set_video_source(struct ast_variable *headers, struct ast_ari_bridges_set_video_source_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_clear_video_source() */
+struct ast_ari_bridges_clear_video_source_args {
+       /*! Bridge's id */
+       const char *bridge_id;
+};
+/*!
+ * \brief Removes any explicit video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants. When no explicit video source is set, talk detection will be used to determine the active video stream.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_clear_video_source(struct ast_variable *headers, struct ast_ari_bridges_clear_video_source_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_bridges_start_moh() */
 struct ast_ari_bridges_start_moh_args {
        /*! Bridge's id */
index a60b7010a30d59a14ced39865f26c5f02971fc7a..79610cda1518457070f91f11b22b7d7aae1bee8e 100644 (file)
@@ -769,6 +769,129 @@ fin: __attribute__((unused))
        ast_free(args.channel);
        return;
 }
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/videoSource/{channelId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_bridges_set_video_source_cb(
+       struct ast_tcptls_session_instance *ser,
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct ast_ari_response *response)
+{
+       struct ast_ari_bridges_set_video_source_args args = {};
+       struct ast_variable *i;
+       RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               {}
+       }
+       ast_ari_bridges_set_video_source(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 0: /* Implementation is still a stub, or the code wasn't set */
+               is_valid = response->message == NULL;
+               break;
+       case 500: /* Internal Server Error */
+       case 501: /* Not Implemented */
+       case 404: /* Bridge or Channel not found */
+       case 409: /* Channel not in Stasis application */
+       case 422: /* Channel not in this Bridge */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_void(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/videoSource/{channelId}\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/videoSource/{channelId}\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+       return;
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/videoSource.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_bridges_clear_video_source_cb(
+       struct ast_tcptls_session_instance *ser,
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct ast_ari_response *response)
+{
+       struct ast_ari_bridges_clear_video_source_args args = {};
+       struct ast_variable *i;
+       RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "bridgeId") == 0) {
+                       args.bridge_id = (i->value);
+               } else
+               {}
+       }
+       ast_ari_bridges_clear_video_source(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 0: /* Implementation is still a stub, or the code wasn't set */
+               is_valid = response->message == NULL;
+               break;
+       case 500: /* Internal Server Error */
+       case 501: /* Not Implemented */
+       case 404: /* Bridge not found */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_void(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/videoSource\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/videoSource\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+       return;
+}
 int ast_ari_bridges_start_moh_parse_body(
        struct ast_json *body,
        struct ast_ari_bridges_start_moh_args *args)
@@ -1471,6 +1594,25 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
        .children = {  }
 };
 /*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_videoSource_channelId = {
+       .path_segment = "channelId",
+       .is_wildcard = 1,
+       .callbacks = {
+               [AST_HTTP_POST] = ast_ari_bridges_set_video_source_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_videoSource = {
+       .path_segment = "videoSource",
+       .callbacks = {
+               [AST_HTTP_DELETE] = ast_ari_bridges_clear_video_source_cb,
+       },
+       .num_children = 1,
+       .children = { &bridges_bridgeId_videoSource_channelId, }
+};
+/*! \brief REST handler for /api-docs/bridges.json */
 static struct stasis_rest_handlers bridges_bridgeId_moh = {
        .path_segment = "moh",
        .callbacks = {
@@ -1517,8 +1659,8 @@ static struct stasis_rest_handlers bridges_bridgeId = {
                [AST_HTTP_GET] = ast_ari_bridges_get_cb,
                [AST_HTTP_DELETE] = ast_ari_bridges_destroy_cb,
        },
-       .num_children = 5,
-       .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
+       .num_children = 6,
+       .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
 };
 /*! \brief REST handler for /api-docs/bridges.json */
 static struct stasis_rest_handlers bridges = {
index 3926149c2423c925b210b9960d0e7404034bfd85..7fa5cae53582b7a558118fdd8d37e4e63fee3e6c 100644 (file)
@@ -700,6 +700,13 @@ static void sub_bridge_update_handler(void *data,
                json = simple_bridge_event("BridgeDestroyed", old_snapshot, tv);
        } else if (!old_snapshot) {
                json = simple_bridge_event("BridgeCreated", new_snapshot, tv);
+       } else if (new_snapshot && old_snapshot
+               && strcmp(new_snapshot->video_source_id, old_snapshot->video_source_id)) {
+               json = simple_bridge_event("BridgeVideoSourceChanged", new_snapshot, tv);
+               if (json && !ast_strlen_zero(old_snapshot->video_source_id)) {
+                       ast_json_object_set(json, "old_video_source_id",
+                               ast_json_string_create(old_snapshot->video_source_id));
+               }
        }
 
        if (json) {
index ab2c6c2d54532d047fd2422b518d21572b998f38..8289b43e1a2c1fb190efe088a5204395bab719d3 100644 (file)
                                }
                        ]
                },
+               {
+                       "path": "/bridges/{bridgeId}/videoSource/{channelId}",
+                       "description": "Set a channel as the video source in a multi-party bridge",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Set a channel as the video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants.",
+                                       "nickname": "setVideoSource",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Bridge or Channel not found"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel not in Stasis application"
+                                               },
+                                               {
+                                                       "code": 422,
+                                                       "reason": "Channel not in this Bridge"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
+               {
+                       "path": "/bridges/{bridgeId}/videoSource",
+                       "description": "Removes any explicit video source",
+                       "operations": [
+                               {
+                                       "httpMethod": "DELETE",
+                                       "summary": "Removes any explicit video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants. When no explicit video source is set, talk detection will be used to determine the active video stream.",
+                                       "nickname": "clearVideoSource",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "bridgeId",
+                                                       "description": "Bridge's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Bridge not found"
+                                               }
+                                       ]
+                               }
+                       ]
+               },
                {
                        "path": "/bridges/{bridgeId}/moh",
                        "description": "Play music on hold to a bridge",
                                        "type": "List[string]",
                                        "description": "Ids of channels participating in this bridge",
                                        "required": true
+                               },
+                               "video_mode": {
+                                       "type": "string",
+                                       "description": "The video mode the bridge is using. One of 'none', 'talker', or 'single'.",
+                                       "required": false
+                               },
+                               "video_source_id": {
+                                       "type": "string",
+                                       "description": "The ID of the channel that is the source of video in this bridge, if one exists.",
+                                       "required": false
                                }
                        }
                }
index 4ef1d21a49a35d858c7711f2d11df893801c83d0..f99f52e67beda6631490e7f743c291b22be83f53 100644 (file)
                                "BridgeMerged",
                                "BridgeBlindTransfer",
                                "BridgeAttendedTransfer",
+                               "BridgeVideoSourceChanged",
                                "ChannelCreated",
                                "ChannelDestroyed",
                                "ChannelEnteredBridge",
                                }
                        }
                },
+               "BridgeVideoSourceChanged": {
+                       "id": "BridgeVideoSourceChanged",
+                       "description": "Notification that the source of video in a bridge has changed.",
+                       "properties": {
+                               "bridge": {
+                                       "required": true,
+                                       "type": "Bridge"
+                               },
+                               "old_video_source_id": {
+                                       "required": false,
+                                       "type": "string"
+                               }
+                       }
+               },
                "BridgeBlindTransfer": {
                        "id": "BridgeBlindTransfer",
                        "description": "Notification that a blind transfer has occurred.",