CCXML 1.0-W3C Development GuideHome  |  Frameset Home

  tutorial Outbound CCXML Token Dialing  |  TOC  |  tutorial Using JSON, Send, and Foreach  

Tutorial: Multi-party Conferencing in CCXML 1.0

This lesson will show you how to create a multi-party conferencing application utilizing CCXML 1.0, where any amount of callers may connect to a given "conferenceID code", and then communicate with the other parties already joined. Or a new caller may specify a different conference code, and then start a new conference for other parties to join. This tutorial is fairly complex, and assumes you have completed the previous lessons. To be clear, this tutorial is not recommended as a starting point, as it leverages concepts detailed fully in previous dissertaions on the CCXML 1.0 markup.

In this Lesson, we will:

Note: this tutorial requires the enabling of outdial privileges on the Voxeo network, and the provisioning of a alphanumeric token string to your application. In order to get hooked up with all these neat features, check our Support Guide for all the juicy details.

Note: The code presented within this tutorial is intended for the "Prophecy - CCXML 1.0" application type only. Attempts to utilize this code on the "CCXML - Voxeo" application type will not function, as the latter platform is based on the W3C specification from 2002.  Prophecy CCXML 1.0 is compliant to the most recent specification from 2006.

Step 1: setting up the connection

Since we are well aware of how to start out a CCXML 1.0 document from our previous tutorial, we won't revisit the basics in detail here. Instead, we will start out by creating our initial structure along with defining a few variables that we will need later on in the development process for our VXML dialogs, and our inbound connections. These variables, and what they do, are detailed below:




<?xml version="1.0" encoding="UTF-8" ?>
<ccxml version="1.0">

<!-- Connection ID Vars -->
<var name="call_0"/>
<var name="myConfID"/>

<!-- Dialog ID Vars -->
<var name="getConfID_Dlg"/>
<var name="joinTheConf_Dlg"/>
<var name="leftTheConf_Dlg"/>
<var name="noInput_Dlg"/>


<!-- App Vars -->
<var name="nextEvent"/>
<var name="audioPath" expr="'audio/'"/>
<var name="state_0" expr="'init'"/>


<eventprocessor statevariable="state_0">

</eventprocessor>

</ccxml>



We are also going to write our application in a somewhat asynchronous manner, as compared to our other tutorials: Previous tutorial code was written in an almost top-down order of execution, but for the purposes of this tutorial, we are going to segregate our <transition> events based on category: connection, conference, dialog, and error. Writing your CCXML 1.0 using this methodology can be really helpful in understanding the correlation between events, and some may feel that this sort of document structure makes the application code easier to read. Others may disagree, but these folks will just have to Lump It for the duration of this tutorial.

As our goal here is to write an indial application that will allow any callers to hit a CCXML 1.0 front end, and allow the caller to join any specific conference ID that they choose, our first obvious step is to define our "connection" events. These will doubtlessly look familiar to those who have run the gamut of our previous tutorials:


<!-- *********************** -->
<!-- Connection Events -->
<!-- *********************** -->


  <transition state="init" event="connection.alerting">
    <assign name="call_0" expr="event$.connectionid"/>
    <accept connectionid="call_0"/>
  </transition>

  <transition state="init" event="connection.connected">
    <log expr="'*** CALL CONNECTED ***'"/>
    <assign name="state_0" expr="'connected'"/>
    <send name="'getConfID'" target="session.id" targettype="'ccxml'"/>
  </transition>

  <transition event="connection.disconnected">
    <log expr="'*** CALL DISCONNECTED ***'"/>
    <exit/>
  </transition>


Here, we set the event handlers for all the various Connection events that can occur in our indial application: One for when the call is alerting, (ie, ringing), one for when the call is actually connected, and finally, one for when the call is disconnected. You'll note that right off the bat, we make use of one of the variables that we previously defined, (the "call_0" variable), and assign it to the value of the callerID of our first caller: this is going to be very important later on, as when we play a VXML dialog to a caller, we need to know which connectionID to play the dialog to, right?

The second thing of note that we take care of in this step is to <send> an event to our CCXML 1.0 session, which, upon the success of this event, will launch the initial VXML dialog prompting our caller to provide the application with a code that denotes the conference ID that he or she wishes to join to. And this particular facet of our development, we will cover next, have no fear.



Step 2: setting up our event handler dialogs

As indicated, our next step is to setup some handlers for the various user-events that we will be tossing back and forth in the execution of our multiparty conferencing application. This section of <transition> events all have several things in common: they are kicked off when a <send> request completes successfully, and all of these events will in turn, launch a VXML dialog to the caller. In addition to this, astute observers will also notice that all the various dialogs that we spawn consistently denote the "call_0" connectionid, (which indicates that yes, the dialog should play to the current call session), and also that each dialog specifies a particular "dialogid". Each "dialogid" value in question is dependent upon caller actions while within the conference itself, which we will cover shortly.

Just so we don't totally lose our path, the order of a successful execution might look something like this:



Now, there are a few steps to this call flow that we have skipped over, those being the "confirmation" dialogs that we play upon caller input: Once a conference ID is input, we execute the "joinTheConf" dialog to let the caller know that, yes, we did receive the input, and let them know that the join to the conference in impending. We also let them know that they can bail out of the conference via a keypress, which will then bring them to another menu alowing further caller action. This other menu, (the "leftTheConf" <transition>), will then present a list of options to rejoin, exit, or start a new conference. And of course, should the caller play dumb when asked for input, we present the dialog within the "playNoInput" <transition> to let them know to wake the heck up.


Note that when we enter each dialog, the value of the "state_0" variable changes via the <assign> tag. This is done so that we can then handle each dialog.exit event seperately, whichallows us to direct the call flow based on the context of the current event that we are in. These specific "dialog.exit" events are detailed in the next step of the tutorial, of course.



<!-- *********************** -->
<!-- User Defined Events -->
<!-- *********************** -->


  <transition event="getConfID">
    <assign name="state_0" expr="'getConfID'"/>
    <dialogstart  src="audioPath + 'enterConferenceID.wav?termdigits=#&amp;maxtime=10000&amp;text=Enter your conference I D followed by the pound key.'"
                  type="'application/x-fetchdigits'"
                  dialogid="getConfID_Dlg"
                  connectionid="call_0"/>
  </transition>

  <transition event="joinTheConf">
    <assign name="state_0" expr="'joinTheConf'"/>
    <dialogstart src="audioPath + 'joinTheConference.wav?text=You will now join the conference. Press the pound key to exit without disconnecting.'"
                type="'audio/wav'"
                dialogid="joinTheConf_Dlg"
                connectionid="call_0"/>
  </transition>

  <transition event="leftTheConf">
    <assign name="state_0" expr="'leftTheConf'"/>
    <dialogstart src="audioPath + 'leftTheConference.wav?termdigits=123&amp;maxtime=30000&amp;text=You have left the conference. Press one to disconnect. Press two to rejoin, or press three to join a different conference.'"
                type="'application/x-fetchdigits'"
                dialogid="leftTheConf_Dlg"
                connectionid="call_0"/>
  </transition>

  <transition event="playNoInput">
    <assign name="state_0" expr="'playNoInput'"/>
    <dialogstart src="audioPath + 'noInput.wav?text=I am sorry. I did not hear anything.'"
                type="'audio/wav'"
                dialogid="noInput_Dlg"
                connectionid="call_0"/>
  </transition>



Step 3: setting up our dialog.exit handlers

In this step of our tutorial, we will get a little bit complex, but complex CCXML 1.0 applications are our Bread and Butter, right? When any of the dialogs in Step 2 have completed, they will throw dialog.exit events that we will need to trap and appropriately handle based on the state that we have already set while in the dialogs themselves.

Most of these dialog.exit events have some commonalities as well. Upon the completion of these event handlers, we are going to <send> an event to the CCXML 1.0 session that will result in a new dialog getting executed, (with the exception of when we just <join> the caller to the conference. Most of these events also update our "nextEvent" variable with a value as well: This is to make sure that in the event that we need to circle back to our "playNoInput" dialog, and re-enter the current dialog, the app will have clearly defined instructions as to which event to throw and transition our caller to. If our caller gives the app the silent treatment, and doesn't enter anything when asked for a conference ID, (or fails to press the termdigit of dtmf-pound), we then execute the "playNoInput" dialog, and upon exiting, will use the value of the "nextEvent" to execute the correct <transition>.


To make things a bit easier to understand, we have four different <transition> events specific to dialog.exit events that we will whip out in this section of the tutorial, and we will list these out with a brief description of when each of these comes into play:

getConfID: This will be executed when after the caller has entered a conference code, (or even no conference code!), followed by the dtmf-pound, (the terminating character defined in the dialog extension). Within this transition, there is some conditional logic that will evaluate the dialog return value, ("event$.values.digits"), and if the there is no conferenceID defined, the state will change so that the user is notified that no valid conference ID was given, within the "playNoInput" <transition> defined in Step 2 above, and the CCXML execution will then prompt the caller to re-enter a conference ID, (hopefully a valid one, this time) within the same "getConfID" <transition> that we just executed. If we did recieve a valid conference ID, then a conference is then created with the "confname" value being set to the conference PIN that the caller entered.

joinTheConf: Once the event generated by the conference creation is handled by the "joinTheConf" dialog handler defined in Step 2, this <transition> will then <join> the caller into the conference after playing a confirmation message to the caller in the "joinTheConf" dialog above. In our dialog.exit, we specify a "voxeo-termdigits" dialog extension attribute that will allow the caller to be unjoined from the conference with the press of the dtmf-pound key, which can, in turn generate an event that can be handled by the "leftTheConf" handlers that we have defined both in this section, and in the "dialog" handlers above.

leftTheConf: In the event that a caller exits the conference via a dtmf-pound keypress, there are several options that are presented. Stepping back to the <transition> of the same name in Step 2, the caller may leave the conference, (dtmf-1), rejoin the conference, (dtmf-2), or join a new conference, (dtmf-3). Depending on the keypress, this dialog.exit <transition> contains some conditional logic that will execute the appropriate actions: either to disconnect the caller, programmatically rejoin the conference, or to bring the caller back to the "getConfID" dialog by sending an event back to the CCXML 1.0 session to be handled by, (you guessed it), the aforementioned dialog <transition> handlers that we discussed in Step 2. We base our conditional logic on the value of the digit that the app returns upon a dialog.exit event. For the "application/x-fetchdigits" dialog extension type, the value that will be returned to the CCXML 1.0 application upon a dialog.exit can always be referenced by using the "event$.values.termdigit" syntax.

playNoInput: Once the user has been reminded that the application needs a numeric conferenceID, this dialog.exit event will execute, and will send the caller back to the "getConfID" dialog for another chance at Glory.



<!-- *********************** -->
<!-- Dialog Exit Events -->
<!-- *********************** -->


  <transition state="getConfID" event="dialog.exit">
    <if cond="event$.values.digits == ''">
      <log expr="'*** USER DID NOT ENTER CONFERENCE ID ***'"/>
      <assign name="nextEvent" expr="'getConfID'"/>
      <send name="'playNoInput'" target="session.id" targettype="'ccxml'"/>

    <else/>
      <log expr="'*** CONFERENCE ID ENTERED: [' + event$.values.digits + '] ***'"/>
      <createconference conferenceid="myConfID" confname="event$.values.digits"/>

    </if>
  </transition>

  <transition state="joinTheConf" event="dialog.exit">
    <log expr="'*** JOINING CONFERENCE ***'"/>
    <join id1="call_0" id2="myConfID" entertone="'true'" exittone="'true'" voxeo-termdigits="'#'"/>
  </transition>

  <transition state="leftTheConf" event="dialog.exit">
    <log expr="'*** USER PRESSED: [' + event$.values.termdigit + '] ***'"/>

    <if cond="event$.values.termdigit == 1">
      <log expr="'*** USER CHOSE TO DISCONNECT ***'"/>
      <disconnect connectionid="call_0"/>

    <elseif cond="event$.values.termdigit == 2"/>
      <log expr="'*** USER CHOSE TO REJOIN CONFERENCE ***'"/>
      <send name="'joinTheConf'" target="session.id" targettype="'ccxml'"/>

    <elseif cond="event$.values.termdigit == 3"/>
      <log expr="'*** USER CHOSE TO JOIN NEW CONFERENCE ***'"/>
      <send name="'getConfID'" target="session.id" targettype="'ccxml'"/>

    <else/>
      <log expr="'*** USER DID NOT PRESS A TERMDIGIT ***'"/>
      <assign name="nextEvent" expr="'leftTheConf'"/>
      <send name="'playNoInput'" target="session.id" targettype="'ccxml'"/>

    </if>
  </transition>

  <transition state="playNoInput" event="dialog.exit">
    <log expr="'*** NOINPUT DIALOG COMPLETE / THROWING NEXT EVENT: [' + nextEvent + '] ***'"/>
    <send name="nextEvent" target="session.id" targettype="'ccxml'"/>
  </transition>




Step 4: setting our conference and error handlers

At last, we come to the real "meat" of our tutorial: Seeing as our object here is to create a multiparty conference, this is where the fun really begins. In this step of our tutorial, we will add in just a few handlers for our conference events, and of course, the catch-all handler to deal with any typos that we may have put into the app that could result in a looped session. As you remember, this event handler should be included in any and all CCXML 1.0 documents, and this tutorial is no exception.

When the directive to create a conference has been sent from the "getConfID" dialog.exit handler above, the result will be that a "conference.created" event will be thrown by the CCXML 1.0 system, which we will undoubtedly want to handle. Of course, the caller isn't going to inherently be joined to this conference, we must do so manually, and the first step to accomplishing this is to <send> an event that will make this happen. The "joinTheConf" event that we send within this <transition> will be trapped within the handler by the same name within our "dialog.exit" event, which you will notice contains the actual <join> directive. And when this occurs, the event will be trapped by the "conference.joined" handler we have below, which will spit out a message in the Logger that confirms when this has completed successfully.

The last conference-related event handler comes into play in the event that a caller programmatically disconnects from the conference, (via a keypress of dtmf-pound as detailed in Step 3). Once this event is trapped, the "conferenec.unjoined" event will then be thrown so that we can handle this caller action in a graceful manner. This graceful manner being that we <send> a 'leftTheConf' event that allows us to invoke the dialog by the same name where the caller options are clearly listed.




<!-- *********************** -->
<!-- Conference Events -->
<!-- *********************** -->
  <transition event="conference.created">
    <log expr="'*** CONFERENCE CREATED ***'"/>
    <send name="'joinTheConf'" target="session.id" targettype="'ccxml'"/>
  </transition>

  <transition event="conference.joined">
    <log expr="'*** CALL JOINED TO CONFERENCE ***'"/>
  </transition>

  <transition event="conference.unjoined">
    <log expr="'*** CALL UNJOINED FROM CONFERENCE ***'"/>
    <send name="'leftTheConf'" target="session.id" targettype="'ccxml'"/>
  </transition>


<!-- *********************** -->
<!-- General Exceptions -->
<!-- *********************** -->

  <transition event="error.*">
    <log expr="'*** AN ERROR HAS OCCURRED: [' + event$.reason + '] ***'"/>
    <exit/>
  </transition>


Our application is now complete, and ready for deployment. The complete code is listed below in it's entirety for your viewing pleasure:



<?xml version="1.0" encoding="UTF-8" ?>
<ccxml version="1.0">


<!-- Connection ID Vars -->
<var name="call_0"/>
<var name="myConfID"/>
<!-- Dialog ID Vars -->
<var name="getConfID_Dlg"/>
<var name="joinTheConf_Dlg"/>
<var name="leftTheConf_Dlg"/>
<var name="noInput_Dlg"/>


<!-- App Vars -->
<var name="nextEvent"/>
<var name="audioPath" expr="'audio/'"/>
<var name="state_0" expr="'init'"/>


<eventprocessor statevariable="state_0">

<!-- *********************** -->
<!-- Connection Events -->
<!-- *********************** -->
  <transition state="init" event="connection.alerting">
    <assign name="call_0" expr="event$.connectionid"/>
    <accept connectionid="call_0"/>
  </transition>

  <transition state="init" event="connection.connected">
    <log expr="'*** CALL CONNECTED ***'"/>
    <assign name="state_0" expr="'connected'"/>
    <send name="'getConfID'" target="session.id" targettype="'ccxml'"/>
  </transition>

  <transition event="connection.disconnected">
    <log expr="'*** CALL DISCONNECTED ***'"/>
    <exit/>
  </transition>

<!-- *********************** -->
<!-- User Defined Events -->
<!-- *********************** -->
  <transition event="getConfID">
    <assign name="state_0" expr="'getConfID'"/>
    <dialogstart  src="audioPath + 'enterConferenceID.wav?termdigits=#&amp;maxtime=10000&amp;text=Enter your conference I D followed by the pound key.'"
                  type="'application/x-fetchdigits'"
                  dialogid="getConfID_Dlg"
                  connectionid="call_0"/>
  </transition>

  <transition event="joinTheConf">
    <assign name="state_0" expr="'joinTheConf'"/>
    <dialogstart src="audioPath + 'joinTheConference.wav?text=You will now join the conference. Press the pound key to exit without disconnecting.'"
                type="'audio/wav'"
                dialogid="joinTheConf_Dlg"
                connectionid="call_0"/>
  </transition>

  <transition event="leftTheConf">
    <assign name="state_0" expr="'leftTheConf'"/>
    <dialogstart src="audioPath + 'leftTheConference.wav?termdigits=123&amp;maxtime=30000&amp;text=You have left the conference. Press one to disconnect. Press two to rejoin, or press three to join a different conference.'"
                type="'application/x-fetchdigits'"
                dialogid="leftTheConf_Dlg"
                connectionid="call_0"/>
  </transition>

  <transition event="playNoInput">
    <assign name="state_0" expr="'playNoInput'"/>
    <dialogstart src="audioPath + 'noInput.wav?text=I am sorry. I did not hear anything.'"
                type="'audio/wav'"
                dialogid="noInput_Dlg"
                connectionid="call_0"/>
  </transition>


<!-- *********************** -->
<!-- Dialog Exit Events -->
<!-- *********************** -->
  <transition state="getConfID" event="dialog.exit">
    <if cond="event$.values.digits == ''">
      <log expr="'*** USER DID NOT ENTER CONFERENCE ID ***'"/>
      <assign name="nextEvent" expr="'getConfID'"/>
      <send name="'playNoInput'" target="session.id" targettype="'ccxml'"/>
    <else/>
      <log expr="'*** CONFERENCE ID ENTERED: [' + event$.values.digits + '] ***'"/>
      <createconference conferenceid="myConfID" confname="event$.values.digits"/>
    </if>
  </transition>

  <transition state="joinTheConf" event="dialog.exit">
    <log expr="'*** JOINING CONFERENCE ***'"/>
    <join id1="call_0" id2="myConfID" entertone="'true'" exittone="'true'" voxeo-termdigits="'#'"/>
  </transition>

  <transition state="leftTheConf" event="dialog.exit">
    <log expr="'*** USER PRESSED: [' + event$.values.termdigit + '] ***'"/>
    <if cond="event$.values.termdigit == 1">
      <log expr="'*** USER CHOSE TO DISCONNECT ***'"/>
      <disconnect connectionid="call_0"/>
    <elseif cond="event$.values.termdigit == 2"/>
      <log expr="'*** USER CHOSE TO REJOIN CONFERENCE ***'"/>
      <send name="'joinTheConf'" target="session.id" targettype="'ccxml'"/>
    <elseif cond="event$.values.termdigit == 3"/>
      <log expr="'*** USER CHOSE TO JOIN NEW CONFERENCE ***'"/>
      <send name="'getConfID'" target="session.id" targettype="'ccxml'"/>
    <else/>
      <log expr="'*** USER DID NOT PRESS A TERMDIGIT ***'"/>
      <assign name="nextEvent" expr="'leftTheConf'"/>
      <send name="'playNoInput'" target="session.id" targettype="'ccxml'"/>
    </if>
  </transition>

  <transition state="playNoInput" event="dialog.exit">
    <log expr="'*** NOINPUT DIALOG COMPLETE / THROWING NEXT EVENT: [' + nextEvent + '] ***'"/>
    <send name="nextEvent" target="session.id" targettype="'ccxml'"/>
  </transition>


<!-- *********************** -->
<!-- Conference Events -->
<!-- *********************** -->
  <transition event="conference.created">
    <log expr="'*** CONFERENCE CREATED ***'"/>
    <send name="'joinTheConf'" target="session.id" targettype="'ccxml'"/>
  </transition>

  <transition event="conference.joined">
    <log expr="'*** CALL JOINED TO CONFERENCE ***'"/>
  </transition>

  <transition event="conference.unjoined">
    <log expr="'*** CALL UNJOINED FROM CONFERENCE ***'"/>
    <send name="'leftTheConf'" target="session.id" targettype="'ccxml'"/>
  </transition>


<!-- *********************** -->
<!-- General Exceptions -->
<!-- *********************** -->

  <transition event="error.*">
    <log expr="'*** AN ERROR HAS OCCURRED: [' + event$.reason + '] ***'"/>
    <exit/>
  </transition>
</eventprocessor>

</ccxml>



  Download the CCXML 1.0 source code!




  ANNOTATIONS: EXISTING POSTS
0 posts - click the button below to add a note to this page

login
  tutorial Outbound CCXML Token Dialing  |  TOC  |  tutorial Using JSON, Send, and Foreach  

© 2008 Voxeo Corporation  |  Voxeo IVR  |  VoiceXML & CCXML IVR Developer Site