/*
* It may be necessary to resume music on hold after we finish
* playing the announcment.
- *
- * XXX We have no idea what MOH class was in use before playing
- * the file.
*/
if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
- ast_moh_start(bridge_channel->chan, NULL, NULL);
+ const char *latest_musicclass;
+
+ ast_channel_lock(bridge_channel->chan);
+ latest_musicclass = ast_strdupa(ast_channel_latest_musicclass(bridge_channel->chan));
+ ast_channel_unlock(bridge_channel->chan);
+ ast_moh_start(bridge_channel->chan, latest_musicclass, NULL);
}
}
* \note Needs to be atomically settable.
*/
enum bridge_channel_thread_state activity;
+ /*! Owed events to the bridge. */
+ struct {
+ /*! Time started sending the current digit. (Invalid if owed.dtmf_digit is zero.) */
+ struct timeval dtmf_tv;
+ /*! Digit currently sending into the bridge. (zero if not sending) */
+ char dtmf_digit;
+ } owed;
};
/*!
*/
struct ast_bridge_channel *bridge_channel_internal_alloc(struct ast_bridge *bridge);
+/*!
+ * \internal
+ * \brief Clear owed events by the channel to the original bridge.
+ * \since 12.0.0
+ *
+ * \param orig_bridge Original bridge the channel was in before leaving.
+ * \param bridge_channel Channel that owes events to the original bridge.
+ *
+ * \note On entry, the orig_bridge is already locked.
+ *
+ * \return Nothing
+ */
+void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct ast_bridge_channel *bridge_channel);
+
/*!
* \internal
* \brief Push the bridge channel into its specified bridge.
DECLARE_STRINGFIELD_SETTERS_FOR(name);
DECLARE_STRINGFIELD_SETTERS_FOR(language);
DECLARE_STRINGFIELD_SETTERS_FOR(musicclass);
+DECLARE_STRINGFIELD_SETTERS_FOR(latest_musicclass);
DECLARE_STRINGFIELD_SETTERS_FOR(accountcode);
DECLARE_STRINGFIELD_SETTERS_FOR(peeraccount);
DECLARE_STRINGFIELD_SETTERS_FOR(userfield);
const char *ast_channel_name(const struct ast_channel *chan);
const char *ast_channel_language(const struct ast_channel *chan);
const char *ast_channel_musicclass(const struct ast_channel *chan);
+const char *ast_channel_latest_musicclass(const struct ast_channel *chan);
const char *ast_channel_accountcode(const struct ast_channel *chan);
const char *ast_channel_peeraccount(const struct ast_channel *chan);
const char *ast_channel_userfield(const struct ast_channel *chan);
void ast_channel_timingfd_set(struct ast_channel *chan, int value);
int ast_channel_visible_indication(const struct ast_channel *chan);
void ast_channel_visible_indication_set(struct ast_channel *chan, int value);
+int ast_channel_hold_state(const struct ast_channel *chan);
+void ast_channel_hold_state_set(struct ast_channel *chan, int value);
int ast_channel_vstreamid(const struct ast_channel *chan);
void ast_channel_vstreamid_set(struct ast_channel *chan, int value);
unsigned short ast_channel_transfercapability(const struct ast_channel *chan);
if (bridge_channel_internal_push(bridge_channel)) {
ast_bridge_channel_leave_bridge(bridge_channel,
BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, bridge_channel->bridge->cause);
+ bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
}
} else {
ast_bridge_channel_leave_bridge(bridge_channel,
BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, bridge_channel->bridge->cause);
+ bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
}
res = -1;
+ } else {
+ bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
}
bridge_reconfigured(dst_bridge, !optimized);
* simple_bridge/native_bridge are likely the only techs that will do this.
*/
bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+
+ /* Remember any owed events to the bridge. */
+ switch (frame->frametype) {
+ case AST_FRAME_DTMF_BEGIN:
+ bridge_channel->owed.dtmf_tv = ast_tvnow();
+ bridge_channel->owed.dtmf_digit = frame->subclass.integer;
+ break;
+ case AST_FRAME_DTMF_END:
+ bridge_channel->owed.dtmf_digit = '\0';
+ break;
+ case AST_FRAME_CONTROL:
+ /*
+ * We explicitly will not remember HOLD/UNHOLD frames because
+ * things like attended transfers will handle them.
+ */
+ default:
+ break;
+ }
ast_bridge_unlock(bridge_channel->bridge);
/*
return 0;
}
+void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct ast_bridge_channel *bridge_channel)
+{
+ if (bridge_channel->owed.dtmf_digit) {
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_DTMF_END,
+ .subclass.integer = bridge_channel->owed.dtmf_digit,
+ .src = "Bridge channel owed DTMF",
+ };
+
+ frame.len = ast_tvdiff_ms(ast_tvnow(), bridge_channel->owed.dtmf_tv);
+ if (frame.len < option_dtmfminduration) {
+ frame.len = option_dtmfminduration;
+ }
+ ast_log(LOG_DTMF, "DTMF end '%c' simulated to bridge %s because %s left. Duration %ld ms.\n",
+ bridge_channel->owed.dtmf_digit, orig_bridge->uniqueid,
+ ast_channel_name(bridge_channel->chan), frame.len);
+ bridge_channel->owed.dtmf_digit = '\0';
+ orig_bridge->technology->write(orig_bridge, NULL, &frame);
+ }
+}
+
/*!
* \internal
* \brief Suspend a channel from a bridge.
/*
* It may be necessary to resume music on hold after we finish
* playing the announcment.
- *
- * XXX We have no idea what MOH class was in use before playing
- * the file. This method also fails to restore ringing indications.
- * the proposed solution is to create a resume_entertainment callback
- * for the bridge technology and execute it here.
*/
if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
- ast_moh_start(bridge_channel->chan, NULL, NULL);
+ const char *latest_musicclass;
+
+ ast_channel_lock(bridge_channel->chan);
+ latest_musicclass = ast_strdupa(ast_channel_latest_musicclass(bridge_channel->chan));
+ ast_channel_unlock(bridge_channel->chan);
+ ast_moh_start(bridge_channel->chan, latest_musicclass, NULL);
}
}
bridge->v_table->name,
bridge->uniqueid);
-/* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */
-/* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */
if (!bridge_channel->just_joined) {
/* Tell the bridge technology we are leaving so they tear us down */
ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology\n",
ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
}
break;
- case AST_CONTROL_HOLD:
- case AST_CONTROL_UNHOLD:
-/*
- * BUGBUG bridge_channels should remember sending/receiving an outstanding HOLD to/from the bridge
- *
- * When the sending channel is pulled from the bridge it needs to write into the bridge an UNHOLD before being pulled.
- * When the receiving channel is pulled from the bridge it needs to generate its own UNHOLD.
- * Something similar needs to be done for DTMF begin/end.
- */
- ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
- break;
case AST_CONTROL_OPTION:
/*
* Forward option Requests, but only ones we know are safe These
ast_bridge_channel_kick(bridge_channel, 0);
ast_frfree(frame);
return;
-/* BUGBUG This is where incoming HOLD/UNHOLD memory should register. Write UNHOLD into bridge when this channel is pulled. */
default:
break;
}
ast_frfree(frame);
return;
}
-/* BUGBUG This is where incoming DTMF begin/end memory should register. Write DTMF end into bridge when this channel is pulled. */
break;
default:
break;
int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
{
int res = 0;
+
ast_format_copy(&bridge_channel->read_format, ast_channel_readformat(bridge_channel->chan));
ast_format_copy(&bridge_channel->write_format, ast_channel_writeformat(bridge_channel->chan));
}
bridge_channel_internal_pull(bridge_channel);
+ bridge_channel_settle_owed_events(bridge_channel->bridge, bridge_channel);
bridge_reconfigured(bridge_channel->bridge, 1);
ast_bridge_unlock(bridge_channel->bridge);
- /* Indicate a source change since this channel is leaving the bridge system. */
- ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+ /* Complete any active hold before exiting the bridge. */
+ if (ast_channel_hold_state(bridge_channel->chan) == AST_CONTROL_HOLD) {
+ ast_debug(1, "Channel %s simulating UNHOLD for bridge end.\n",
+ ast_channel_name(bridge_channel->chan));
+ ast_indicate(bridge_channel->chan, AST_CONTROL_UNHOLD);
+ }
-/* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */
-/* BUGBUG This is where outgoing HOLD/UNHOLD memory should write UNHOLD to channel. */
/* Complete any partial DTMF digit before exiting the bridge. */
if (ast_channel_sending_dtmf_digit(bridge_channel->chan)) {
ast_channel_end_dtmf(bridge_channel->chan,
ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
}
+ /* Indicate a source change since this channel is leaving the bridge system. */
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+
/*
* Wait for any dual redirect to complete.
*
/* Initial state */
ast_channel_state_set(tmp, state);
+ ast_channel_hold_state_set(tmp, AST_CONTROL_UNHOLD);
ast_channel_streamid_set(tmp, -1);
ast_channel_epfd_set(tmp, -1);
#endif
+ ast_channel_hold_state_set(tmp, AST_CONTROL_UNHOLD);
+
ast_channel_internal_setup_topics(tmp);
headp = ast_channel_varshead(tmp);
ast_channel_connected(chan));
}
break;
-
case AST_CONTROL_REDIRECTING:
{
struct ast_party_redirecting redirecting;
ast_party_redirecting_free(&redirecting);
}
break;
-
+ case AST_CONTROL_HOLD:
+ case AST_CONTROL_UNHOLD:
+ ast_channel_hold_state_set(chan, condition);
+ break;
default:
break;
}
int origstate;
int visible_indication;
int clone_was_zombie = 0;/*!< TRUE if the clonechan was a zombie before the masquerade. */
+ int clone_hold_state;
struct ast_frame *current;
const struct ast_channel_tech *t;
void *t_pvt;
free_translation(clonechan);
free_translation(original);
+ clone_hold_state = ast_channel_hold_state(clonechan);
+
/* Save the current DTMF digit being sent if any. */
clone_sending_dtmf_digit = ast_channel_sending_dtmf_digit(clonechan);
clone_sending_dtmf_tv = ast_channel_sending_dtmf_tv(clonechan);
ast_bridge_notify_masquerade(original);
+ if (clone_hold_state == AST_CONTROL_HOLD) {
+ ast_debug(1, "Channel %s simulating UNHOLD for masquerade.\n",
+ ast_channel_name(original));
+ ast_indicate(original, AST_CONTROL_UNHOLD);
+ }
if (clone_sending_dtmf_digit) {
/*
* The clonechan was sending a DTMF digit that was not completed
* (RINGING, CONGESTION, etc.)
*/
if (visible_indication) {
- ast_indicate(original, visible_indication);
+ if (visible_indication == AST_CONTROL_HOLD) {
+ const char *latest_musicclass;
+ int len;
+
+ ast_channel_lock(original);
+ latest_musicclass = ast_strdupa(ast_channel_latest_musicclass(original));
+ ast_channel_unlock(original);
+ if (ast_strlen_zero(latest_musicclass)) {
+ latest_musicclass = NULL;
+ len = 0;
+ } else {
+ len = strlen(latest_musicclass) + 1;
+ }
+ ast_indicate_data(original, visible_indication, latest_musicclass, len);
+ } else {
+ ast_indicate(original, visible_indication);
+ }
}
ast_channel_lock(original);
}
duration = ast_tvdiff_ms(ast_tvnow(), start);
+ if (duration < option_dtmfminduration) {
+ duration = option_dtmfminduration;
+ }
ast_senddigit_end(chan, digit, duration);
ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
digit, ast_channel_name(chan), why, duration);
AST_STRING_FIELD(name); /*!< ASCII unique channel name */
AST_STRING_FIELD(language); /*!< Language requested for voice prompts */
AST_STRING_FIELD(musicclass); /*!< Default music class */
+ AST_STRING_FIELD(latest_musicclass); /*!< Latest active music class */
AST_STRING_FIELD(accountcode); /*!< Account code for billing */
AST_STRING_FIELD(peeraccount); /*!< Peer account code for billing */
AST_STRING_FIELD(userfield); /*!< Userfield for CEL billing */
int epfd;
#endif
int visible_indication; /*!< Indication currently playing on the channel */
+ int hold_state; /*!< Current Hold/Unhold state */
unsigned short transfercapability; /*!< ISDN Transfer Capability - AST_FLAG_DIGITAL is not enough */
DEFINE_STRINGFIELD_SETTERS_FOR(name, 0, 1);
DEFINE_STRINGFIELD_SETTERS_FOR(language, 1, 0);
DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(latest_musicclass, 0, 0);
DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 1, 0);
DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 1, 0);
DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0, 0);
DEFINE_STRINGFIELD_GETTER_FOR(name);
DEFINE_STRINGFIELD_GETTER_FOR(language);
DEFINE_STRINGFIELD_GETTER_FOR(musicclass);
+DEFINE_STRINGFIELD_GETTER_FOR(latest_musicclass);
DEFINE_STRINGFIELD_GETTER_FOR(accountcode);
DEFINE_STRINGFIELD_GETTER_FOR(peeraccount);
DEFINE_STRINGFIELD_GETTER_FOR(userfield);
{
chan->visible_indication = value;
}
+int ast_channel_hold_state(const struct ast_channel *chan)
+{
+ return chan->hold_state;
+}
+void ast_channel_hold_state_set(struct ast_channel *chan, int value)
+{
+ chan->hold_state = value;
+}
int ast_channel_vstreamid(const struct ast_channel *chan)
{
return chan->vstreamid;
}
}
+ ast_channel_latest_musicclass_set(chan, mohclass->name);
ast_set_flag(ast_channel_flags(chan), AST_FLAG_MOH);
if (mohclass->total_files) {