mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-05 04:11:08 +00:00
Make DTMF attended transfer support feature-complete.
This greatly modifies the operation of DTMF attended transfers so that the full range of options from features.conf applies. In addition, a new option has been added that allows for a transferer to switch between bridges during a transfer before completing the transfer. (closes issue ASTERISK-21543) reported by Matt Jordan Review: https://reviewboard.asterisk.org/r/2654 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@395151 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
@@ -54,421 +54,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
#include "asterisk/mixmonitor.h"
|
||||
#include "asterisk/audiohook.h"
|
||||
|
||||
/*!
|
||||
* \brief Helper function that presents dialtone and grabs extension
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on failure
|
||||
*/
|
||||
static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
|
||||
{
|
||||
int res;
|
||||
int digit_timeout;
|
||||
RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
|
||||
|
||||
ast_channel_lock(chan);
|
||||
xfer_cfg = ast_get_chan_features_xfer_config(chan);
|
||||
if (!xfer_cfg) {
|
||||
ast_log(LOG_ERROR, "Unable to get transfer configuration\n");
|
||||
ast_channel_unlock(chan);
|
||||
return -1;
|
||||
}
|
||||
digit_timeout = xfer_cfg->transferdigittimeout;
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
/* Play the simple "transfer" prompt out and wait */
|
||||
res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
|
||||
ast_stopstream(chan);
|
||||
if (res < 0) {
|
||||
/* Hangup or error */
|
||||
return -1;
|
||||
}
|
||||
if (res) {
|
||||
/* Store the DTMF digit that interrupted playback of the file. */
|
||||
exten[0] = res;
|
||||
}
|
||||
|
||||
/* Drop to dialtone so they can enter the extension they want to transfer to */
|
||||
res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, digit_timeout);
|
||||
if (res < 0) {
|
||||
/* Hangup or error */
|
||||
res = -1;
|
||||
} else if (!res) {
|
||||
/* 0 for invalid extension dialed. */
|
||||
if (ast_strlen_zero(exten)) {
|
||||
ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan));
|
||||
} else {
|
||||
ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
|
||||
ast_channel_name(chan), exten, context);
|
||||
}
|
||||
ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
|
||||
res = -1;
|
||||
} else {
|
||||
/* Dialed extension is valid. */
|
||||
res = 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void copy_caller_data(struct ast_channel *dest, struct ast_channel *caller)
|
||||
{
|
||||
ast_channel_lock_both(caller, dest);
|
||||
ast_connected_line_copy_from_caller(ast_channel_connected(dest), ast_channel_caller(caller));
|
||||
ast_channel_inherit_variables(caller, dest);
|
||||
ast_channel_datastore_inherit(caller, dest);
|
||||
ast_channel_unlock(dest);
|
||||
ast_channel_unlock(caller);
|
||||
}
|
||||
|
||||
/*! \brief Helper function that creates an outgoing channel and returns it immediately */
|
||||
static struct ast_channel *dial_transfer(struct ast_channel *caller, const char *exten, const char *context)
|
||||
{
|
||||
char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
|
||||
struct ast_channel *chan;
|
||||
int cause;
|
||||
|
||||
/* Fill the variable with the extension and context we want to call */
|
||||
snprintf(destination, sizeof(destination), "%s@%s", exten, context);
|
||||
|
||||
/* Now we request a local channel to prepare to call the destination */
|
||||
chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
|
||||
&cause);
|
||||
if (!chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Who is transferring the call. */
|
||||
pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", ast_channel_name(caller));
|
||||
|
||||
/* To work as an analog to BLINDTRANSFER */
|
||||
pbx_builtin_setvar_helper(chan, "ATTENDEDTRANSFER", ast_channel_name(caller));
|
||||
|
||||
/* Before we actually dial out let's inherit appropriate information. */
|
||||
copy_caller_data(chan, caller);
|
||||
|
||||
/* Since the above worked fine now we actually call it and return the channel */
|
||||
if (ast_call(chan, destination, 0)) {
|
||||
ast_hangup(chan);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Determine the transfer context to use.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param transferer Channel initiating the transfer.
|
||||
* \param context User supplied context if available. May be NULL.
|
||||
*
|
||||
* \return The context to use for the transfer.
|
||||
*/
|
||||
static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
|
||||
{
|
||||
if (!ast_strlen_zero(context)) {
|
||||
return context;
|
||||
}
|
||||
context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
|
||||
if (!ast_strlen_zero(context)) {
|
||||
return context;
|
||||
}
|
||||
context = ast_channel_macrocontext(transferer);
|
||||
if (!ast_strlen_zero(context)) {
|
||||
return context;
|
||||
}
|
||||
context = ast_channel_context(transferer);
|
||||
if (!ast_strlen_zero(context)) {
|
||||
return context;
|
||||
}
|
||||
return "default";
|
||||
}
|
||||
|
||||
static void blind_transfer_cb(struct ast_channel *new_channel, void *user_data,
|
||||
enum ast_transfer_type transfer_type)
|
||||
{
|
||||
struct ast_channel *transferer_channel = user_data;
|
||||
|
||||
if (transfer_type == AST_BRIDGE_TRANSFER_MULTI_PARTY) {
|
||||
copy_caller_data(new_channel, transferer_channel);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Internal built in feature for blind transfers */
|
||||
static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
|
||||
{
|
||||
char exten[AST_MAX_EXTENSION] = "";
|
||||
struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
|
||||
const char *context;
|
||||
char *goto_on_blindxfr;
|
||||
|
||||
ast_bridge_channel_write_hold(bridge_channel, NULL);
|
||||
|
||||
ast_channel_lock(bridge_channel->chan);
|
||||
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
|
||||
blind_transfer ? blind_transfer->context : NULL));
|
||||
goto_on_blindxfr = ast_strdupa(S_OR(pbx_builtin_getvar_helper(bridge_channel->chan,
|
||||
"GOTO_ON_BLINDXFR"), ""));
|
||||
ast_channel_unlock(bridge_channel->chan);
|
||||
|
||||
/* Grab the extension to transfer to */
|
||||
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(goto_on_blindxfr)) {
|
||||
ast_debug(1, "After transfer, transferer %s goes to %s\n",
|
||||
ast_channel_name(bridge_channel->chan), goto_on_blindxfr);
|
||||
ast_after_bridge_set_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr);
|
||||
}
|
||||
|
||||
if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb,
|
||||
bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS &&
|
||||
!ast_strlen_zero(goto_on_blindxfr)) {
|
||||
ast_after_bridge_goto_discard(bridge_channel->chan);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Attended transfer code */
|
||||
enum atxfer_code {
|
||||
/*! Party C hungup or other reason to abandon the transfer. */
|
||||
ATXFER_INCOMPLETE,
|
||||
/*! Transfer party C to party A. */
|
||||
ATXFER_COMPLETE,
|
||||
/*! Turn the transfer into a threeway call. */
|
||||
ATXFER_THREEWAY,
|
||||
/*! Hangup party C and return party B to the bridge. */
|
||||
ATXFER_ABORT,
|
||||
};
|
||||
|
||||
/*! \brief Attended transfer feature to complete transfer */
|
||||
static int attended_transfer_complete(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
|
||||
{
|
||||
enum atxfer_code *transfer_code = hook_pvt;
|
||||
|
||||
*transfer_code = ATXFER_COMPLETE;
|
||||
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Attended transfer feature to turn it into a threeway call */
|
||||
static int attended_transfer_threeway(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
|
||||
{
|
||||
enum atxfer_code *transfer_code = hook_pvt;
|
||||
|
||||
*transfer_code = ATXFER_THREEWAY;
|
||||
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Attended transfer feature to abort transfer */
|
||||
static int attended_transfer_abort(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
|
||||
{
|
||||
enum atxfer_code *transfer_code = hook_pvt;
|
||||
|
||||
*transfer_code = ATXFER_ABORT;
|
||||
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Internal built in feature for attended transfers */
|
||||
static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
|
||||
{
|
||||
char exten[AST_MAX_EXTENSION] = "";
|
||||
struct ast_channel *peer;
|
||||
struct ast_bridge *attended_bridge;
|
||||
struct ast_bridge_features caller_features;
|
||||
int xfer_failed;
|
||||
struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
|
||||
const char *complete_sound;
|
||||
const char *context;
|
||||
enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
|
||||
const char *atxfer_abort;
|
||||
const char *atxfer_threeway;
|
||||
const char *atxfer_complete;
|
||||
const char *fail_sound;
|
||||
RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
|
||||
|
||||
ast_bridge_channel_write_hold(bridge_channel, NULL);
|
||||
|
||||
bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
|
||||
|
||||
ast_channel_lock(bridge_channel->chan);
|
||||
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
|
||||
attended_transfer ? attended_transfer->context : NULL));
|
||||
xfer_cfg = ast_get_chan_features_xfer_config(bridge_channel->chan);
|
||||
if (!xfer_cfg) {
|
||||
ast_log(LOG_ERROR, "Unable to get transfer configuration options\n");
|
||||
ast_channel_unlock(bridge_channel->chan);
|
||||
return 0;
|
||||
}
|
||||
if (attended_transfer) {
|
||||
atxfer_abort = ast_strdupa(S_OR(attended_transfer->abort, xfer_cfg->atxferabort));
|
||||
atxfer_threeway = ast_strdupa(S_OR(attended_transfer->threeway, xfer_cfg->atxferthreeway));
|
||||
atxfer_complete = ast_strdupa(S_OR(attended_transfer->complete, xfer_cfg->atxfercomplete));
|
||||
} else {
|
||||
atxfer_abort = ast_strdupa(xfer_cfg->atxferabort);
|
||||
atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway);
|
||||
atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete);
|
||||
}
|
||||
fail_sound = ast_strdupa(xfer_cfg->xferfailsound);
|
||||
ast_channel_unlock(bridge_channel->chan);
|
||||
|
||||
/* Grab the extension to transfer to */
|
||||
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get a channel that is the destination we wish to call */
|
||||
peer = dial_transfer(bridge_channel->chan, exten, context);
|
||||
if (!peer) {
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
|
||||
/* Setup a DTMF menu to control the transfer. */
|
||||
if (ast_bridge_features_init(&caller_features)
|
||||
|| ast_bridge_hangup_hook(&caller_features,
|
||||
attended_transfer_complete, &transfer_code, NULL, 0)
|
||||
|| ast_bridge_dtmf_hook(&caller_features, atxfer_abort,
|
||||
attended_transfer_abort, &transfer_code, NULL, 0)
|
||||
|| ast_bridge_dtmf_hook(&caller_features, atxfer_complete,
|
||||
attended_transfer_complete, &transfer_code, NULL, 0)
|
||||
|| ast_bridge_dtmf_hook(&caller_features, atxfer_threeway,
|
||||
attended_transfer_threeway, &transfer_code, NULL, 0)) {
|
||||
ast_bridge_features_cleanup(&caller_features);
|
||||
ast_hangup(peer);
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create a bridge to use to talk to the person we are calling */
|
||||
attended_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX,
|
||||
AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
|
||||
if (!attended_bridge) {
|
||||
ast_bridge_features_cleanup(&caller_features);
|
||||
ast_hangup(peer);
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
ast_bridge_merge_inhibit(attended_bridge, +1);
|
||||
|
||||
/* This is how this is going down, we are imparting the channel we called above into this bridge first */
|
||||
/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
|
||||
if (ast_bridge_impart(attended_bridge, peer, NULL, NULL, 0)) {
|
||||
ast_bridge_destroy(attended_bridge);
|
||||
ast_bridge_features_cleanup(&caller_features);
|
||||
ast_hangup(peer);
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For the caller we want to join the bridge in a blocking
|
||||
* fashion so we don't spin around in this function doing
|
||||
* nothing while waiting.
|
||||
*/
|
||||
ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL, 0);
|
||||
|
||||
/*
|
||||
* BUGBUG there is a small window where the channel does not point to the bridge_channel.
|
||||
*
|
||||
* This window is expected to go away when atxfer is redesigned
|
||||
* to fully support existing functionality. There will be one
|
||||
* and only one ast_bridge_channel structure per channel.
|
||||
*/
|
||||
/* Point the channel back to the original bridge and bridge_channel. */
|
||||
ast_bridge_channel_lock(bridge_channel);
|
||||
ast_channel_lock(bridge_channel->chan);
|
||||
ast_channel_internal_bridge_channel_set(bridge_channel->chan, bridge_channel);
|
||||
ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
|
||||
ast_channel_unlock(bridge_channel->chan);
|
||||
ast_bridge_channel_unlock(bridge_channel);
|
||||
|
||||
/* Wait for peer thread to exit bridge and die. */
|
||||
if (!ast_autoservice_start(bridge_channel->chan)) {
|
||||
ast_bridge_depart(peer);
|
||||
ast_autoservice_stop(bridge_channel->chan);
|
||||
} else {
|
||||
ast_bridge_depart(peer);
|
||||
}
|
||||
|
||||
/* Now that all channels are out of it we can destroy the bridge and the feature structures */
|
||||
ast_bridge_destroy(attended_bridge);
|
||||
ast_bridge_features_cleanup(&caller_features);
|
||||
|
||||
/* Is there a courtesy sound to play to the peer? */
|
||||
ast_channel_lock(bridge_channel->chan);
|
||||
complete_sound = pbx_builtin_getvar_helper(bridge_channel->chan,
|
||||
"ATTENDED_TRANSFER_COMPLETE_SOUND");
|
||||
if (!ast_strlen_zero(complete_sound)) {
|
||||
complete_sound = ast_strdupa(complete_sound);
|
||||
} else {
|
||||
complete_sound = NULL;
|
||||
}
|
||||
ast_channel_unlock(bridge_channel->chan);
|
||||
if (complete_sound) {
|
||||
pbx_builtin_setvar_helper(peer, "BRIDGE_PLAY_SOUND", complete_sound);
|
||||
}
|
||||
|
||||
xfer_failed = -1;
|
||||
switch (transfer_code) {
|
||||
case ATXFER_INCOMPLETE:
|
||||
/* Peer hungup */
|
||||
break;
|
||||
case ATXFER_COMPLETE:
|
||||
/* The peer takes our place in the bridge. */
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
|
||||
xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1);
|
||||
break;
|
||||
case ATXFER_THREEWAY:
|
||||
/*
|
||||
* Transferer wants to convert to a threeway call.
|
||||
*
|
||||
* Just impart the peer onto the bridge and have us return to it
|
||||
* as normal.
|
||||
*/
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1);
|
||||
break;
|
||||
case ATXFER_ABORT:
|
||||
/* Transferer decided not to transfer the call after all. */
|
||||
break;
|
||||
}
|
||||
ast_bridge_merge_inhibit(bridge, -1);
|
||||
ao2_ref(bridge, -1);
|
||||
if (xfer_failed) {
|
||||
ast_hangup(peer);
|
||||
if (!ast_check_hangup_locked(bridge_channel->chan)) {
|
||||
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
|
||||
}
|
||||
ast_bridge_channel_write_unhold(bridge_channel);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum set_touch_variables_res {
|
||||
SET_TOUCH_SUCCESS,
|
||||
SET_TOUCH_UNSET,
|
||||
@@ -909,8 +494,6 @@ static int unload_module(void)
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
ast_bridge_features_register(AST_BRIDGE_BUILTIN_BLINDTRANSFER, feature_blind_transfer, NULL);
|
||||
ast_bridge_features_register(AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, feature_attended_transfer, NULL);
|
||||
ast_bridge_features_register(AST_BRIDGE_BUILTIN_HANGUP, feature_hangup, NULL);
|
||||
ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMON, feature_automonitor, NULL);
|
||||
ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMIXMON, feature_automixmonitor, NULL);
|
||||
|
@@ -396,6 +396,8 @@ struct ast_bridge_methods {
|
||||
struct ast_bridge {
|
||||
/*! Bridge virtual method table. */
|
||||
const struct ast_bridge_methods *v_table;
|
||||
/*! "Personality" currently exhibited by bridge subclass */
|
||||
void *personality;
|
||||
/*! Immutable bridge UUID. */
|
||||
char uniqueid[AST_UUID_STR_LEN];
|
||||
/*! Bridge technology that is handling the bridge */
|
||||
@@ -1785,6 +1787,16 @@ struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast
|
||||
*/
|
||||
struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan);
|
||||
|
||||
/*!
|
||||
* \brief Remove marked bridge channel feature hooks.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param features Bridge features structure
|
||||
* \param flags Determinator for whether hook is removed.
|
||||
*
|
||||
* \return Nothing
|
||||
*/
|
||||
void ast_bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags flags);
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
@@ -183,6 +183,8 @@ struct ast_bridge_hook_timer {
|
||||
enum ast_bridge_hook_remove_flags {
|
||||
/*! The hook is removed when the channel is pulled from the bridge. */
|
||||
AST_BRIDGE_HOOK_REMOVE_ON_PULL = (1 << 0),
|
||||
/*! The hook is removed when the bridge's personality changes. */
|
||||
AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE = (1 << 1),
|
||||
};
|
||||
|
||||
/* BUGBUG Need to be able to selectively remove DTMF, hangup, and interval hooks. */
|
||||
@@ -265,6 +267,8 @@ struct ast_bridge_features_attended_transfer {
|
||||
char threeway[MAXIMUM_DTMF_FEATURE_STRING];
|
||||
/*! DTMF string used to complete the transfer (If not empty.) */
|
||||
char complete[MAXIMUM_DTMF_FEATURE_STRING];
|
||||
/*! DTMF string used to swap bridged targets (If not empty.) */
|
||||
char swap[MAXIMUM_DTMF_FEATURE_STRING];
|
||||
};
|
||||
|
||||
enum ast_bridge_features_monitor {
|
||||
|
88
include/asterisk/bridging_internal.h
Normal file
88
include/asterisk/bridging_internal.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013 Digium, Inc.
|
||||
*
|
||||
* Mark Michelson <mmichelson@digium.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \file
|
||||
* \brief Private Bridging API
|
||||
*
|
||||
* \author Mark Michelson <mmichelson@digium.com>
|
||||
*
|
||||
* See Also:
|
||||
* \arg \ref AstCREDITS
|
||||
*/
|
||||
|
||||
#ifndef _ASTERISK_PRIVATE_BRIDGING_H
|
||||
#define _ASTERISK_PRIVATE_BRIDGING_H
|
||||
|
||||
struct ast_bridge;
|
||||
struct ast_bridge_channel;
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Move a bridge channel from one bridge to another.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param dst_bridge Destination bridge of bridge channel move.
|
||||
* \param bridge_channel Channel moving from one bridge to another.
|
||||
* \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
|
||||
* \param optimized Indicates whether the move is part of an unreal channel optimization.
|
||||
*
|
||||
* \note The dst_bridge and bridge_channel->bridge are assumed already locked.
|
||||
*
|
||||
* \retval 0 on success.
|
||||
* \retval -1 on failure.
|
||||
*/
|
||||
int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel,
|
||||
int attempt_recovery, unsigned int optimized);
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Do the merge of two bridges.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param dst_bridge Destination bridge of merge.
|
||||
* \param src_bridge Source bridge of merge.
|
||||
* \param kick_me Array of channels to kick from the bridges.
|
||||
* \param num_kick Number of channels in the kick_me array.
|
||||
* \param optimized Indicates whether the merge is part of an unreal channel optimization.
|
||||
*
|
||||
* \return Nothing
|
||||
*
|
||||
* \note The two bridges are assumed already locked.
|
||||
*
|
||||
* This moves the channels in src_bridge into the bridge pointed
|
||||
* to by dst_bridge.
|
||||
*/
|
||||
void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge,
|
||||
struct ast_bridge_channel **kick_me, unsigned int num_kick, unsigned int optimized);
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Helper function to find a bridge channel given a channel.
|
||||
*
|
||||
* \param bridge What to search
|
||||
* \param chan What to search for.
|
||||
*
|
||||
* \note On entry, bridge is already locked.
|
||||
*
|
||||
* \retval bridge_channel if channel is in the bridge.
|
||||
* \retval NULL if not in bridge.
|
||||
*/
|
||||
struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan);
|
||||
|
||||
#endif /* _ASTERISK_PRIVATE_BRIDGING_H */
|
@@ -63,6 +63,37 @@ void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_n
|
||||
*/
|
||||
int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value);
|
||||
|
||||
/*!
|
||||
* \brief Check if a role exists on a channel
|
||||
*
|
||||
* \param channel The channel to check
|
||||
* \param role_name The name of the role to search for
|
||||
*
|
||||
* \retval 0 The requested role does not exist on the channel
|
||||
* \retval 1 The requested role exists on the channel
|
||||
*
|
||||
* This is an alternative to \ref ast_bridge_channel_has_role that is useful if bridge
|
||||
* roles have not yet been established on a channel's bridge_channel. A possible example of
|
||||
* when this could be used is in a bridge v_table's push() callback.
|
||||
*/
|
||||
int ast_channel_has_role(struct ast_channel *channel, const char *role_name);
|
||||
|
||||
/*!
|
||||
* \brief Retrieve the value of a requested role option from a channel
|
||||
*
|
||||
* \param channel The channel to retrieve the requested option from
|
||||
* \param role_name The role to which the option belongs
|
||||
* \param option The name of the option to retrieve
|
||||
*
|
||||
* \retval NULL The option does not exist
|
||||
* \retval non-NULL The value of the option
|
||||
*
|
||||
* This is an alternative to \ref ast_bridge_channel_get_role_option that is useful if bridge
|
||||
* roles have not yet been esstablished on a channel's bridge_channel. A possible example of
|
||||
* when this could be used is in a bridge v_table's push() callback.
|
||||
*/
|
||||
const char *ast_channel_get_role_option(struct ast_channel *channel, const char *role_name, const char *option);
|
||||
|
||||
/*!
|
||||
* \brief Check to see if a bridge channel inherited a specific role from its channel
|
||||
*
|
||||
@@ -72,7 +103,7 @@ int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *
|
||||
* \retval 0 The bridge channel does not have the requested role
|
||||
* \retval 1 The bridge channel does have the requested role
|
||||
*
|
||||
* \note Before a bridge_channel can effectively check roles against a bridge, ast_bridge_roles_bridge_channel_establish_roles
|
||||
* \note Before a bridge_channel can effectively check roles against a bridge, ast_bridge_channel_establish_roles
|
||||
* should be called on the bridge_channel so that roles and their respective role options can be copied from the channel
|
||||
* datastore into the bridge_channel roles list. Otherwise this function will just return 0 because the list will be NULL.
|
||||
*/
|
||||
@@ -88,10 +119,10 @@ int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const
|
||||
* \retval NULL If either the role does not exist on the bridge_channel or the role does exist but the option has not been set
|
||||
* \retval The value of the option
|
||||
*
|
||||
* \note See ast_bridge_roles_channel_set_role_option note about the need to call ast_bridge_roles_bridge_channel_establish_roles.
|
||||
* \note See ast_channel_set_role_option note about the need to call ast_bridge_channel_establish_roles.
|
||||
*
|
||||
* \note The returned character pointer is only valid as long as the bridge_channel is guaranteed to be alive and hasn't had
|
||||
* ast_bridge_roles_bridge_channel_clear_roles called against it (as this will free all roles and role options in the bridge
|
||||
* ast_bridge_channel_clear_roles called against it (as this will free all roles and role options in the bridge
|
||||
* channel). If you need this value after one of these destruction events occurs, you must make a local copy while it is
|
||||
* still valid.
|
||||
*/
|
||||
@@ -119,12 +150,12 @@ int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel
|
||||
* \param bridge_channel the bridge channel that we are scrubbing
|
||||
*
|
||||
* \details
|
||||
* If roles are already established on a bridge channel, ast_bridge_roles_bridge_channel_establish_roles will fail unconditionally
|
||||
* If roles are already established on a bridge channel, ast_bridge_channel_establish_roles will fail unconditionally
|
||||
* without changing any roles. In order to update a bridge channel's roles, they must first be cleared from the bridge channel using
|
||||
* this function.
|
||||
*
|
||||
* \note
|
||||
* ast_bridge_roles_bridge_channel_clear_roles also serves as the destructor for the role list of a bridge channel.
|
||||
* ast_bridge_channel_clear_roles also serves as the destructor for the role list of a bridge channel.
|
||||
*/
|
||||
void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel);
|
||||
|
||||
|
@@ -66,6 +66,8 @@ struct ast_features_xfer_config {
|
||||
AST_STRING_FIELD(atxfercomplete);
|
||||
/*! DTMF sequence used to turn an attempted atxfer into a three-way call */
|
||||
AST_STRING_FIELD(atxferthreeway);
|
||||
/*! DTMF sequence used to swap which party the transferer is talking to */
|
||||
AST_STRING_FIELD(atxferswap);
|
||||
);
|
||||
/*! Milliseconds allowed between digit presses when dialing transfer destination */
|
||||
unsigned int transferdigittimeout;
|
||||
|
@@ -256,6 +256,8 @@ enum ast_attended_transfer_dest_type {
|
||||
AST_ATTENDED_TRANSFER_DEST_APP,
|
||||
/*! The transfer results in both bridges remaining with a local channel linking them */
|
||||
AST_ATTENDED_TRANSFER_DEST_LINK,
|
||||
/*! The transfer results in a threeway call between transferer, transferee, and transfer target */
|
||||
AST_ATTENDED_TRANSFER_DEST_THREEWAY,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -279,6 +281,8 @@ struct ast_attended_transfer_message {
|
||||
char app[AST_MAX_APP];
|
||||
/*! Pair of local channels linking the bridges. Applicable for AST_ATTENDED_TRANSFER_DEST_LINK */
|
||||
struct ast_channel_snapshot *links[2];
|
||||
/*! Transferer channel and bridge that survived the transition to a threeway call. Applicable for AST_ATTENDED_TRANSFER_DEST_THREEWAY */
|
||||
struct ast_bridge_channel_snapshot_pair threeway;
|
||||
} dest;
|
||||
};
|
||||
|
||||
@@ -328,6 +332,25 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast
|
||||
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
|
||||
struct ast_bridge *final_bridge);
|
||||
|
||||
/*!
|
||||
* \since 12
|
||||
* \brief Publish an attended transfer that results in a threeway call.
|
||||
*
|
||||
* Publish an \ref ast_attended_transfer_message with the dest_type set to
|
||||
* \c AST_ATTENDED_TRANSFER_DEST_THREEWAY. Like with \ref ast_bridge_publish_attended_transfer_bridge_merge,
|
||||
* this results from merging two bridges together. The difference is that a
|
||||
* transferer channel survives the bridge merge
|
||||
*
|
||||
* \param is_external Indicates if the transfer was initiated externally
|
||||
* \param result The result of the transfer.
|
||||
* \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
|
||||
* \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
|
||||
* \param final_pair The bridge that the parties end up in, and the transferer channel that is in this bridge.
|
||||
*/
|
||||
void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result,
|
||||
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
|
||||
struct ast_bridge_channel_pair *final_pair);
|
||||
|
||||
/*!
|
||||
* \since 12
|
||||
* \brief Publish an attended transfer that results in an application being run
|
||||
|
@@ -63,6 +63,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
#include "asterisk/core_local.h"
|
||||
#include "asterisk/core_unreal.h"
|
||||
#include "asterisk/features_config.h"
|
||||
#include "asterisk/bridging_internal.h"
|
||||
|
||||
/*! All bridges container. */
|
||||
static struct ao2_container *bridges;
|
||||
@@ -77,7 +78,6 @@ static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
|
||||
|
||||
static void cleanup_video_mode(struct ast_bridge *bridge);
|
||||
static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
|
||||
static void bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags);
|
||||
|
||||
/*! Default DTMF keys for built in features */
|
||||
static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
|
||||
@@ -463,19 +463,7 @@ void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channe
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Helper function to find a bridge channel given a channel.
|
||||
*
|
||||
* \param bridge What to search
|
||||
* \param chan What to search for.
|
||||
*
|
||||
* \note On entry, bridge is already locked.
|
||||
*
|
||||
* \retval bridge_channel if channel is in the bridge.
|
||||
* \retval NULL if not in bridge.
|
||||
*/
|
||||
static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
|
||||
struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
|
||||
{
|
||||
struct ast_bridge_channel *bridge_channel;
|
||||
|
||||
@@ -757,7 +745,7 @@ static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
|
||||
|| ast_bridge_channel_establish_roles(bridge_channel)) {
|
||||
ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
|
||||
bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
|
||||
bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
return -1;
|
||||
}
|
||||
bridge_channel->in_bridge = 1;
|
||||
@@ -1719,7 +1707,7 @@ static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *
|
||||
*/
|
||||
static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
|
||||
{
|
||||
bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4164,24 +4152,7 @@ static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_chann
|
||||
ao2_ref(old_bridge, -1);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Do the merge of two bridges.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param dst_bridge Destination bridge of merge.
|
||||
* \param src_bridge Source bridge of merge.
|
||||
* \param kick_me Array of channels to kick from the bridges.
|
||||
* \param num_kick Number of channels in the kick_me array.
|
||||
*
|
||||
* \return Nothing
|
||||
*
|
||||
* \note The two bridges are assumed already locked.
|
||||
*
|
||||
* This moves the channels in src_bridge into the bridge pointed
|
||||
* to by dst_bridge.
|
||||
*/
|
||||
static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick,
|
||||
void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick,
|
||||
unsigned int optimized)
|
||||
{
|
||||
struct ast_bridge_channel *bridge_channel;
|
||||
@@ -4440,21 +4411,7 @@ int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridg
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Move a bridge channel from one bridge to another.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param dst_bridge Destination bridge of bridge channel move.
|
||||
* \param bridge_channel Channel moving from one bridge to another.
|
||||
* \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
|
||||
*
|
||||
* \note The dst_bridge and bridge_channel->bridge are assumed already locked.
|
||||
*
|
||||
* \retval 0 on success.
|
||||
* \retval -1 on failure.
|
||||
*/
|
||||
static int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery,
|
||||
int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery,
|
||||
unsigned int optimized)
|
||||
{
|
||||
struct ast_bridge *orig_bridge;
|
||||
@@ -5646,17 +5603,7 @@ static void hooks_remove_heap(struct ast_heap *hooks, enum ast_bridge_hook_remov
|
||||
ast_heap_unlock(hooks);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Remove marked bridge channel feature hooks.
|
||||
* \since 12.0.0
|
||||
*
|
||||
* \param features Bridge features structure
|
||||
* \param remove_flags Determinator for whether hook is removed.
|
||||
*
|
||||
* \return Nothing
|
||||
*/
|
||||
static void bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags)
|
||||
void ast_bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags)
|
||||
{
|
||||
hooks_remove_container(features->dtmf_hooks, remove_flags);
|
||||
hooks_remove_container(features->hangup_hooks, remove_flags);
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -374,6 +374,26 @@ int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *
|
||||
return setup_bridge_role_option(role, option, value);
|
||||
}
|
||||
|
||||
int ast_channel_has_role(struct ast_channel *channel, const char *role_name)
|
||||
{
|
||||
return get_role_from_channel(channel, role_name) ? 1 : 0;
|
||||
}
|
||||
|
||||
const char *ast_channel_get_role_option(struct ast_channel *channel, const char *role_name, const char *option)
|
||||
{
|
||||
struct bridge_role *role;
|
||||
struct bridge_role_option *role_option;
|
||||
|
||||
role = get_role_from_channel(channel, role_name);
|
||||
if (!role) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
role_option = get_role_option(role, option);
|
||||
|
||||
return role_option ? role_option->value : NULL;
|
||||
}
|
||||
|
||||
int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name)
|
||||
{
|
||||
if (!bridge_channel->bridge_roles) {
|
||||
|
@@ -1433,9 +1433,10 @@ static void cel_attended_transfer_cb(
|
||||
switch (xfer->dest_type) {
|
||||
case AST_ATTENDED_TRANSFER_DEST_FAIL:
|
||||
return;
|
||||
/* handle these two the same */
|
||||
/* handle these three the same */
|
||||
case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
|
||||
case AST_ATTENDED_TRANSFER_DEST_LINK:
|
||||
case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
|
||||
extra = ast_json_pack("{s: s, s: s, s: s}",
|
||||
"bridge1_id", bridge1->uniqueid,
|
||||
"channel2_name", channel2->name,
|
||||
|
@@ -3212,37 +3212,37 @@ static int setup_bridge_features_builtin(struct ast_bridge_features *features, s
|
||||
if (!builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf))
|
||||
&& !ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
if (!builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)) &&
|
||||
!ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
}
|
||||
if (ast_test_flag(flags, AST_FEATURE_DISCONNECT) &&
|
||||
!builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)) &&
|
||||
!ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
if (ast_test_flag(flags, AST_FEATURE_PARKCALL) &&
|
||||
!builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)) &&
|
||||
!ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
if (ast_test_flag(flags, AST_FEATURE_AUTOMON) &&
|
||||
!builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)) &&
|
||||
!ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON) &&
|
||||
!builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)) &&
|
||||
!ast_strlen_zero(dtmf)) {
|
||||
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
|
||||
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
|
||||
}
|
||||
|
||||
return res ? -1 : 0;
|
||||
|
@@ -106,6 +106,16 @@
|
||||
the transferees, and the transfer target all being in a single bridge together.</para>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="atxferswap" default="*4">
|
||||
<synopsis>Digits to dial to toggle who the transferrer is currently bridged to during an attended transfer</synopsis>
|
||||
<description>
|
||||
<para>This option is only available to the transferrer during an attended
|
||||
transfer operation. Pressing this DTMF sequence will result in the transferrer swapping
|
||||
which party he is bridged with. For instance, if the transferrer is currently bridged with
|
||||
the transfer target, then pressing this DTMF sequence will cause the transferrer to be
|
||||
bridged with the transferees.</para>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="pickupexten" default="*8">
|
||||
<synopsis>Digits used for picking up ringing calls</synopsis>
|
||||
<description>
|
||||
@@ -355,6 +365,7 @@
|
||||
#define DEFAULT_ATXFER_ABORT "*1"
|
||||
#define DEFAULT_ATXFER_COMPLETE "*2"
|
||||
#define DEFAULT_ATXFER_THREEWAY "*3"
|
||||
#define DEFAULT_ATXFER_SWAP "*4"
|
||||
|
||||
/*! Default pickup options */
|
||||
#define DEFAULT_PICKUPEXTEN "*8"
|
||||
@@ -846,6 +857,8 @@ static int xfer_set(struct ast_features_xfer_config *xfer, const char *name,
|
||||
ast_string_field_set(xfer, atxfercomplete, value);
|
||||
} else if (!strcasecmp(name, "atxferthreeway")) {
|
||||
ast_string_field_set(xfer, atxferthreeway, value);
|
||||
} else if (!strcasecmp(name, "atxferswap")) {
|
||||
ast_string_field_set(xfer, atxferswap, value);
|
||||
} else {
|
||||
/* Unrecognized option */
|
||||
res = -1;
|
||||
@@ -879,6 +892,8 @@ static int xfer_get(struct ast_features_xfer_config *xfer, const char *field,
|
||||
ast_copy_string(buf, xfer->atxfercomplete, len);
|
||||
} else if (!strcasecmp(field, "atxferthreeway")) {
|
||||
ast_copy_string(buf, xfer->atxferthreeway, len);
|
||||
} else if (!strcasecmp(field, "atxferswap")) {
|
||||
ast_copy_string(buf, xfer->atxferswap, len);
|
||||
} else {
|
||||
/* Unrecognized option */
|
||||
res = -1;
|
||||
@@ -1629,6 +1644,8 @@ static int load_config(void)
|
||||
DEFAULT_ATXFER_COMPLETE, xfer_handler, 0);
|
||||
aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options,
|
||||
DEFAULT_ATXFER_THREEWAY, xfer_handler, 0);
|
||||
aco_option_register_custom(&cfg_info, "atxferswap", ACO_EXACT, global_options,
|
||||
DEFAULT_ATXFER_SWAP, xfer_handler, 0);
|
||||
|
||||
aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options,
|
||||
DEFAULT_PICKUPEXTEN, pickup_handler, 0);
|
||||
|
@@ -221,12 +221,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
<enum name="Bridge"><para>The transfer was accomplished by merging two bridges into one.</para></enum>
|
||||
<enum name="App"><para>The transfer was accomplished by having a channel or bridge run a dialplan application.</para></enum>
|
||||
<enum name="Link"><para>The transfer was accomplished by linking two bridges together using a local channel pair.</para></enum>
|
||||
<enum name="Threeway"><para>The transfer was accomplished by placing all parties into a threeway call.</para></enum>
|
||||
<enum name="Fail"><para>The transfer failed.</para></enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
<parameter name="DestBridgeUniqueid">
|
||||
<para>Indicates the surviving bridge when bridges were merged to complete the transfer</para>
|
||||
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal></para></note>
|
||||
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal> or <literal>Threeway</literal></para></note>
|
||||
</parameter>
|
||||
<parameter name="DestApp">
|
||||
<para>Indicates the application that is running when the transfer completes</para>
|
||||
@@ -333,6 +334,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
<parameter name="LocalTwoUniqueid">
|
||||
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
|
||||
</parameter>
|
||||
<parameter name="DestTransfererChannel">
|
||||
<para>The name of the surviving transferer channel when a transfer results in a threeway call</para>
|
||||
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Threeway</literal></para></note>
|
||||
</parameter>
|
||||
</syntax>
|
||||
<description>
|
||||
<para>The headers in this event attempt to describe all the major details of the attended transfer. The two transferer channels
|
||||
@@ -835,6 +840,11 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes
|
||||
ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local1_state));
|
||||
ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local2_state));
|
||||
break;
|
||||
case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
|
||||
ast_str_append(&variable_data, 0, "DestType: Threeway\r\n");
|
||||
ast_str_append(&variable_data, 0, "DestBridgeUniqueid: %s\r\n", transfer_msg->dest.threeway.bridge_snapshot->uniqueid);
|
||||
ast_str_append(&variable_data, 0, "DestTransfererChannel: %s\r\n", transfer_msg->dest.threeway.channel_snapshot->name);
|
||||
break;
|
||||
case AST_ATTENDED_TRANSFER_DEST_FAIL:
|
||||
ast_str_append(&variable_data, 0, "DestType: Fail\r\n");
|
||||
break;
|
||||
@@ -941,6 +951,39 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast
|
||||
stasis_publish(ast_bridge_topic_all(), msg);
|
||||
}
|
||||
|
||||
void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result,
|
||||
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
|
||||
struct ast_bridge_channel_pair *final_pair)
|
||||
{
|
||||
RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
|
||||
|
||||
transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
|
||||
if (!transfer_msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_THREEWAY;
|
||||
if (final_pair->channel == transferee->channel) {
|
||||
transfer_msg->dest.threeway.channel_snapshot = transfer_msg->to_transferee.channel_snapshot;
|
||||
} else {
|
||||
transfer_msg->dest.threeway.channel_snapshot = transfer_msg->to_transfer_target.channel_snapshot;
|
||||
}
|
||||
|
||||
if (final_pair->bridge == transferee->bridge) {
|
||||
transfer_msg->dest.threeway.bridge_snapshot = transfer_msg->to_transferee.bridge_snapshot;
|
||||
} else {
|
||||
transfer_msg->dest.threeway.bridge_snapshot = transfer_msg->to_transfer_target.bridge_snapshot;
|
||||
}
|
||||
|
||||
msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
stasis_publish(ast_bridge_topic_all(), msg);
|
||||
}
|
||||
|
||||
void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer_result result,
|
||||
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
|
||||
const char *dest_app)
|
||||
|
Reference in New Issue
Block a user