CCXML Conference Support

Some applications may want a more complex group of connections and dialogs than a simple <join> can provide. A conference allows up to ten entities (any combination of connections and dialogs) to be joined together and hear the mixed output of the other entities in the conference.

For example, a simple conference with two calls and a VoiceXML dialog joined to it would result in each call being able to hear the output of the VoiceXML dialog and anything the other caller said; the VoiceXML dialog would receive the mixed input from both callers and transmit audio to both calls.

Note that a VoiceXML dialog started on a connection that is part of a conference will neither receive inputs from nor send output to the connection unless it is joined to the conference.

Blueworx Voice Response does not support having more than one dialog started on the same connection - if you want multiple dialogs to be part of your conference then you must ensure that they have been started on different connections.

The CCXML specification does allow for dialogs to start on conferences by supplying a conferenceid instead of a connectionid - this is not supported on Blueworx Voice Response. Instead please start the dialog on a connection, then join the dialog to the conference.

Conference Management

Conferences and their members are managed by the <createconference>, <destroyconference>, <join> and <unjoin> tags. Additionally <createcall> can specify a joinid to join to a conference once the outbound call has been answered.

<createconference> creates a new conference with no connections or dialogs attached to it yet. You can optionally give the conference a name, accessible under the conferencename property of the conference object. This will generate a conference.created event.

<destroyconference> cleans up the conference. Any remaining entities joined to the conference will be unjoined from it automatically.

<unjoin> removes the link between two entities. Implicit conferences made by joining two connections will be cleaned up when enough entities are removed so that only one entity is still in the conference; conferences created by <createconference> must be destroyed by <destroyconference>s or by exiting the CCXML session.

<join> connects two entities (conferences, connection and dialogs), specified by the id1 and id2 attributes of the <join> element. There are some limitations on what can be joined to what, as follows: There are also three settings that are important on <join> elements - duplex, dtmfclamp and dtmftransmitclamp. When joining to a conference, this only affects the leg between that entity and the conference; when joining two connections directly it affects both the leg from connection 1 to the implicit conference and the leg from the implicit conference to connection 2.

Example joins:

	<transition event="conference.created" state="conferencing">
		<log expr="'**** conference.created ****'"/>
		<var name="hints" expr="new Object()"/>
		<assign name="hints.dtmftransmitclamp" expr="true"/>
		<join id1="conferenceid" id2="connectionid1" dtmfclamp="true" hints="hints"/>
		<join id1="conferenceid" id2="connectionid2" dtmfclamp="true"/>
	</transition>

The above CCXML will connect two connections to a conference when it is created. Connection 1 will be unable to send or receive dtmfs from other conference members, whereas connection 2 will be able to send DTMFs to other conference members but not receive them from other conference members (though presently they will not be sent to the only other conference member as connection1 is dtmfclamped). Both connections will send and receive audio from conference members, as duplex is not set.

Later in the same CCXML application a dialog is added to the conference:

<transition event="dialog.started" state="collect">
	<log expr="'**** dialog.started, joining dialog to conference ****'"/>
	<join id1="conferenceid" id2="dialogid" dtmfclamp="false"/>
</transition>

The dialog is joined to the conference and can now send/receive both audio and dtmfs from conference members. Please note that as dtmfclamp defaults to true it is critical that it is set to false when joining dialogs that use dtmfs. In this application only connection 2 would be able to enter DTMFs into a VoiceXML grammar

It should be noted that dtmfclamp and dtmftransmitclamp settings do not stop the generation of connection.signal events on connections if BVR is configured to generate those events. For more information, see Responding to DTMFs in CCXML

Example duplex joins:

	<transition event="conference.created" state="conferencing">
		<log expr="'**** conference.created ****'"/>
		<join id1="conferenceid" id2="presenter_connectionid" duplex="'full'"/>
		<join id1="listener_connectionid1" id2="conferenceid" duplex="'half'"/>
		<join id1="listener_connectionid2" id2="conferenceid" duplex="'half'"/>
		<join id1="listener_connectionid3" id2="conferenceid" duplex="'half'"/>
		<join id1="listener_connectionid4" id2="conferenceid" duplex="'half'"/>
	</transition>

Here the connection that the presenter has called in on is connected as full duplex, so can both hear and speak to other conference members (although in this instance will hear nothing because no other conference members are currently sending audio to the conference). Four listeners are joined to the call - none of them can add any audio to the conference, but all of them hear audio from the conference (in this case, just the presenter). Rejoining the same conference members will update any settings between those two parties - for example, perhaps this application could change a listener connection to full duplex if they enter a DTMF passcode using connection.signal events

Sample application

The below application connects an incoming call to an agent and makes a call out to another sip address, which is also joined to the conference. If the agent presses *7, the call to another sip address is dropped and the calls are rejoined to update their dtmf settings so that the original caller can enter their credit card number into a VoiceXML dialog

Note that this application uses the connection.signal event, so requires the "connection_signal=true" line in the ccxml section of bvr.config to function.

<?xml version="1.0" encoding="UTF-8"?>
<ccxml xmlns="http://www.w3.org/2002/09/ccxml" version="1.0">
        <var name="callerid" expr="''"/>
        <var name="otherIVRid" expr="''"/>
        <var name="agentid" expr="''"/>
        <var name="conferenceid" expr="''"/>
        <var name="statevar" expr="'init'"/>
        <var name="dialogid" expr="''"/>
        <var name="lastDTMF" expr="''"/>

        <var name="agentsip" expr="'sip:agent@agent_IP_here'"/>
        <var name="otherIVRToCall" expr="'sip:application@far_end_application_IP_here'"/>
        <var name="ringingVXML" expr="'ringback.vxml'"/>
        <var name="collectVXML" expr="'collect.vxml'"/>
        <var name="transmitClampHints" expr="new Object()"/>

        <eventprocessor statevariable="statevar">
                <transition event="ccxml.loaded">
                        <log expr="'**** Loaded example CCXML for credit card payment after agent hits *7 ****'"/>
                        <assign name="transmitClampHints.dtmftransmitclamp" expr="true"/>
                </transition>

                <transition event="connection.alerting">
                        <log expr="'**** Connection.alerting, accepting call ****'"/>
                        <accept/>
                </transition>

                <transition event="connection.progressing"/>

                <transition event="connection.connected" state="init">
                        <log expr="'**** Connection.connected ****'"/>
                        <assign name="statevar" expr="'calling'"/>
                        <assign name="callerid" expr="event$.connectionid"/>
                        <dialogstart src="ringingVXML" connectionid="callerid" dialogid="dialogid"/>
                </transition>

                <transition event="error.dialog.notstarted" state="calling">
                        <log expr="'**** Error.dialog.notstarted with reason ' + event$.reason + ', exiting'"/>
                        <disconnect connectionid="callerid"/>
                        <exit/>
                </transition>

                <transition event="dialog.started" state="calling">
                        <createcall dest="agentsip" connectionid="agentid"/>
                </transition>

                <transition event="connection.connected" state="calling">
                        <log expr="'**** Connection.connected for agent, creating conference ****'"/>
                        <dialogterminate dialogid="dialogid"/>
                        <assign name="dialogid" expr="''"/>
                        <assign name="statevar" expr="'conferencing'"/>
                        <createconference confname="'myConferenceName'" conferenceid="conferenceid"/>
                </transition>

                <transition event="conference.created" state="conferencing">
                        <log expr="'**** conference.connected ****'"/>
                        <join id1="conferenceid" id2="callerid" dtmfclamp="true" hints="transmitClampHints"/>
                        <join id1="conferenceid" id2="agentid" dtmfclamp="true"/>
                        <createcall dest="otherIVRToCall" connectionid="otherIVRid"/>
                </transition>

                <transition event="connection.connected" state="conferencing">
                        <log expr="'**** Connection.connected for far end app, waiting for *7 from agent ****'"/>
                        <assign name="statevar" expr="'waiting'"/>
                        <join id1="conferenceid" id2="otherIVRid" dtmfclamp="false"/>
                </transition>

                <transition event="connection.signal" state="waiting">
                        <log expr="'**** connection.signal on call ' + event$.connectionid + ' dtmf ' + event$.dtmf + ' ****'"/>
                        <if cond="event$.connectionid == agentid">
                                <if cond="event$.dtmf == '7' &amp;&amp; lastDTMF == '*'">
                                        <assign name="statevar" expr="'cleanup'"/>
                                        <disconnect connectionid="otherIVRid"/>

                                        <!-- switch the dtmfclamp settings -->
                                        <join id1="conferenceid" id2="callerid" dtmfclamp="true"/>
                                        <join id1="conferenceid" id2="agentid" dtmfclamp="true" hints="transmitClampHints"/>
                                <elseif cond="event$.dtmf == '*7'"/>
                                        <assign name="statevar" expr="'cleanup'"/>
                                        <disconnect connectionid="otherIVRid"/>
                                        <join id1="conferenceid" id2="callerid" dtmfclamp="true"/>
                                        <join id1="conferenceid" id2="agentid" dtmfclamp="true" hints="transmitClampHints"/>
                                <else/>
                                        <assign name="lastDTMF" expr="event$.dtmf"/>
                                </if>
                        <else/>
                                <log expr="'Non-agent DTMF ' + event$.dtmf + ' pressed'"/>
                        </if>
                </transition>

                <transition event="connection.signal"/> <!-- This is stop us logging connection.signal messages when we don't care about them -->

                <transition event="connection.disconnected" state="cleanup">
                        <!-- ok, we need to do payment collection on the caller -->
                        <dialogstart src="collectVXML" connectionid="callerid" dialogid="dialogid"/>
                        <assign name="statevar" expr="'collect'"/>
                </transition>

                <transition event="dialog.started" state="collect">
                        <log expr="'**** dialog.started, joining dialog to conference ****'"/>
                        <join id1="conferenceid" id2="dialogid" dtmfclamp="false"/>
                </transition>

                <transition event="dialog.exit" state="collect">
                		<if cond="event$.values == null">
                        	<log expr="'**** dialog.exit, no return data ****'"/>
                        <else/>
                        	<log expr="'**** dialog.exit, return data is ' + event$.values.cc_number + ' ****'"/>
                        </if>
                        <assign name="statevar" expr="'end'"/>
                        <disconnect connectionid="callerid"/>
                        <disconnect connectionid="agentid"/>
                        <destroyconference conferenceid="conferenceid"/>
                </transition>

                <transition event="connection.disconnected" state="end"/>
                <transition event="connection.disconnected">
                        <log expr="'**** unexpected connection.disconnected, terminating calls and exiting ****'"/>
                        <if cond="event$.connectionid == agentid">
                                <disconnect connectionid="callerid"/>
                        <else/>
                                <disconnect connectionid="agentid"/>
                        </if>
                        <if cond="conferenceid != ''">
                                <destroyconference conferenceid="conferenceid"/>
                        </if>
                        <exit/>
                </transition>

                <transition event="conference.destroyed">
                        <log expr="'**** conference.destroyed, exiting ****'"/>
                        <exit/>
                </transition>

                <transition event="error.*">
                        <log expr="'**** ' + event$.name + ' exiting program due to ' + event$.reason + ' ****'"/>
                        <exit/>
                </transition>

                <transition event="*">
                        <log expr="'**** ' + event$.name + ' dropped through to catchall, state is ' + statevar + ' ****'"/>
                </transition>
        </eventprocessor>
</ccxml>