Oracle® Communications Converged Application Server Developer's Guide Release 5.1 Part Number E27707-01 |
|
|
PDF · Mobi · ePub |
This chapter describes how to implement conferencing applications with media server interactions using Service Foundation Toolkit (SFT).
A common use of a media server is to provide multi-user conferences that incorporate audio and video conferencing features. A conference is a unique instance of a multi-party conversation, and consists of a Focus and a Mixer.
The Focus identifies a conference. It acts as a SIP UA and is addressed by SIP URI. The Focus maintains a SIP signaling relationship with each participant in the conference, and is responsible for ensuring that each participant receives the media that makes up the conference. The Focus implements conference policies, such as determining who can join a conference, and is responsible for interpreting the media policies.
The Mixer is responsible for mixing all members' data streams and sending them back to all conference members via an RTP channel. Data streams may be text, audio, and video. A Mixer is always under the control of the Focus. The Mixer enforces the appropriate media policies. The Mixer represents a JSR 309 compliant media server.
The Focus
interface let's you create a voice conference with a Mixer, such that you can use the JSR 309 APIs to create a Mixer, and encapsulate the Mixer within the Focus
interface to create a conference.
Conferencing and Media Control makes use of the following classes in the com.oracle.sft.api
package. For more information on these interfaces and their usage, refer to the Converged Application Server API Reference.
Class | Description |
---|---|
|
|
Table 23-2 lists methods accessed from the CommunicationSession
interface that you can use to create a conference.
Table 23-2 Methods Defined by the CommunicationSession Interface
Method | Description |
---|---|
|
Creates a conference without a specified name. |
|
Creates a conference with a specified name and focus instance. |
|
Create a conference from a Conversation instance using the focus instance. If no name is specified (null), the conference name will use the name assigned to the focus. |
Example 23-0 creates a JSR 309-compliant Mixer which is encapsulated by the Focus interface. The Mixer is created using the javax.media.mscontrol.mixer.MediaMixer
class, which serves as the base for a conferencing service. When a conference is instantiated, JSR 309 objects are created using MsControlFactory
. The Focus
interface encapsulates the Mixer
, and connects and mixes multiple participants in a conference.
Example 23-1 Creating a Mixer And Encapsulating It Within A Focus
... String msJndiName = "mediaServerJNDIName"; MsControlFactory msFactory = (MsControlFactory)(new InitialContext()).lookup(msJndiName); MediaSession mSession = msFactory.createMediaSession(); MediaMixer mixer = (MediaMixer) mSession.createMediaMixer(MediaMixer.AUDIO_VIDEO); Focus focus = sess.createParticipant(Focus.class, "FOCUS_AUDIO_VIDEO", mixer); Conference conf = sess.createConference(null, call, focus); ...
Example 23-2 creates the Java class ConfFocusBean
, which is a JSR 309 conference application. ConfFocusBean
encapsulates the Mixer within the Focus interface, and illustrates the use of the @CommunicationEvent
annotation in creating and establishing communication within the conference, and @ParticpantEvent
annotation in joining SIP dialogs within the conference.
Example 23-2 Creating A JSR 309 Conference Using The Focus Interface
package com.oracle.sft.testapp; import javax.media.mscontrol.MediaSession; import javax.media.mscontrol.MsControlException; import javax.media.mscontrol.MsControlFactory; import javax.media.mscontrol.join.Joinable; import javax.media.mscontrol.mixer.MediaMixer; import javax.naming.InitialContext; import javax.naming.NamingException; import com.oracle.sft.api.Communication; import com.oracle.sft.api.CommunicationContext; import com.oracle.sft.api.CommunicationService; import com.oracle.sft.api.CommunicationSession; import com.oracle.sft.api.Conference; import com.oracle.sft.api.Context; import com.oracle.sft.api.Conversation; import com.oracle.sft.api.Focus; import com.oracle.sft.api.bean.CommunicationBean; import com.oracle.sft.api.bean.CommunicationEvent; import com.oracle.sft.api.bean.ParticipantEvent; @ServiceAttributes(mscontrolJndiName = "mscontrol.OCMP") @ServiceAttributes(mscontrolJndiName = "mscontrol.dlg309") @CommunicationBean public class ConfFocusBean { @Context CommunicationSession sess; @Context CommunicationContext ctx; @Context CommunicationService service; @CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION) void handleInit() { Conversation call = (Conversation) ctx.getCommunication(); String calleeName = call.getCallee().getName(); if(calleeName.equals("conf1@example.com")) { MediaMixer mixer = this.createMixer(); Focus focus = sess.createParticipant(Focus.class, "FOCUS_AUDIO_VIDEO", mixer); // The Conference name is set to null, and uses the Focus's name. Conference conf = sess.createConference(null, call, focus); } } @CommunicationEvent(type = CommunicationEvent.Type.STARTED) void handleStarted() { Joinable j = ctx.getParticipant().getJoinable(); Communication c = ctx.getCommunication(); } @CommunicationEvent(type = CommunicationEvent.Type.ESTABLISHED) void handleEstablised() { Joinable j = ctx.getParticipant().getJoinable(); Communication c = ctx.getCommunication(); } @ParticipantEvent(type = ParticipantEvent.Type.JOINED) void handleJoin() { Communication c = ctx.getCommunication(); } private MediaMixer createMixer() { String msJndiName = "__SYSTEM.resource.jvb-ra#javax.media.mscontrol.MsControlFactory"; MsControlFactory msFactory; try { msFactory = (MsControlFactory)(new InitialContext()).lookup(msJndiName); MediaSession mSession = msFactory.createMediaSession(); MediaMixer mixer = (MediaMixer) mSession.createMediaMixer(MediaMixer.AUDIO_VIDEO); return mixer; } catch (NamingException e) { e.printStackTrace(); } catch (MsControlException e) { e.printStackTrace(); } return null; } }
In certain types of multimedia communications, a SIP request is distributed to a group of SIP User Agents (UAs). The sender sends a single SIP request to a server which further distributes the request to the group. This SIP request contains a list of Uniform Resource Identifiers (URIs). The URI list is expressed as an XML document that allows the sender of the request to qualify a recipient using a copy control level similar to the copy control level of existing e-mail systems.
The XML resource list (defined in RFC 4826) provides a mechanism for describing a list of resources, however, there is a need for a copy control attribute to determine whether a resource is receiving a SIP request as a primary recipient, a carbon copy, or a blind carbon copy. This is similar to e-mail systems where the sender can categorize each recipient as “to”, “cc”, or “bcc.” RFC 5366 defines the copy control XML extension to the XML resource list format. Example 23-3 shows a list that follows the XML resource list document extended with format extension for representing copy control attributes in resource lists.
Example 23-3 URI List for Conference Establishment Contained in the SIP Header
<?xml version="1.0" encoding="UTF-8"?> <resource-lists xmlns="urn:ietf:params:xml:ns:resource-lists" xmlns:cp="urn:ietf:params:xml:ns:copycontrol"> <list> <entry uri="sip:bill@example.com" cp:copyControl="to"/> <entry uri="sip:joe@example.com" cp:copyControl="cc" /> <entry uri="sip:ted@example.net" cp:copyControl="bcc"/> </list> </resource-lists>
A conference server supporting RFC 5366, in which a received INVITE triggers the conference focus UAS to initiate multiple INVITEs as a UAC, operates as a media termination B2BUA when performing that function.
When an incoming INVITE request with the SIP Content-Type header containing either of:
Content-Type: application/resource-lists+xml
Content-Type: multipart/mixed;boundary="boundary1"
with sub Content-Type: application/resource-lists+xml
is received the com.oracle.sft.api.ResourceListsMessage
interface creates the message, and sets it to the current CommunicationContext
.
The ResourceListsMessage
interface represents the ProtocolMessage
(the actual SIP Message) interface. Like other types of Message
interfaces, ProtocolMessage
is available to the application from the CommunicationContext.getMessage()
method. Note that ProtocolMessage
is expected to be used only by advanced users. Also, changes made to the actual protocol objects may impact the behavior of the SFT runtime. Typically an SFT application adds or removes custom headers or parameters needed by a specific application.
com.oracle.sft.api.UserParticipant
contains recipient list history information.
The interface defines the methods setRecipientListHistory(ResourceLists recipientLists)
and getRecipientListHistory()
that you can use to set and retrieve recipient list history information contained by the URI list in the INVITE to the user participant.
When an INVITE is sent to the user participant, the SIP Content-Type is multipart/mixed
.
Table 23-3 lists the interfaces defined in the com.oracle.sft.api.rls
package to handle the XML resource list carried in the INVITE request. For more information on these interfaces and their usage, refer to the Converged Application Server API Reference.
Table 23-3 Interfaces for Handling XML Resource Lists
Interfaces | Description |
---|---|
|
Represents the display-nameType complex type. |
|
Represents the entryType complex type |
|
Represents the entry-refType complex type. |
|
Represents the externalType complex type. |
|
Represents the |
|
Represents the data carried in the ResourceList. |
|
Represents the |
|
Factory interface for creating resource lists elements. |
Example 23-4 illustrates how to create a conference using a resource list provided in the SIP message from the conference initiator.
Example 23-4 Using the ResourceLists Interface
... Message msg = ctx.getMessage(); if (msg instanceof ResourceListsMessage) { ResourceLists rlss = ((ResourceListsMessage) msg).getResourceLists(); } ... ResourceListsFactory rlssFactory = service.createResourceListsFactory(); ResourceLists recipientLists = rlssFactory.createRecipientLists(rlss); ResourceLists recipientHistoryLists = rlssFactory.createRecipientHistoryLists(rlss); List<ResourceList> dataList = recipientLists.getResourceList(); for (ResourceList rls : dataList) { List<Entry> entryList = rls.getResourceListData(); for (Entry entry: entryList) { String userName = entry.getName(); UserParticipant up = sess.createParticipant(UserParticipant.class, userName); up.setRecipientHistoryList(recipientHistLists); comm.addParticipant(up); } }
Example 23-5 creates a conference using a resource-contained list. A CommunicationBean
named ConfRLSBean
is created, which creates a conference and distributes the request to a group using a resource-contained list to specify copy control to the individual UAS receiving the INVITE. This SIP request contains a list of Uniform Resource Identifier (URI) requests— expressed as an XML document—that allows the sender of the request to qualify a recipient using a copy control level similar to the copy control level of existing e-mail systems.
Example 23-5 Creating a Conference using a Resource-Contained List
package com.oracle.sft.testapp; import java.util.List; import java.util.logging.Logger; import com.oracle.sft.api.Agent; import com.oracle.sft.api.Communication; import com.oracle.sft.api.CommunicationContext; import com.oracle.sft.api.CommunicationService; import com.oracle.sft.api.CommunicationSession; import com.oracle.sft.api.Conference; import com.oracle.sft.api.Context; import com.oracle.sft.api.Conversation; import com.oracle.sft.api.Message; import com.oracle.sft.api.ResourceListsMessage; import com.oracle.sft.api.UserParticipant; import com.oracle.sft.api.bean.CommunicationBean; import com.oracle.sft.api.bean.CommunicationEvent; import com.oracle.sft.api.bean.ParticipantEvent; import com.oracle.sft.api.rls.Entry; import com.oracle.sft.api.rls.ResourceList; import com.oracle.sft.api.rls.ResourceLists; import com.oracle.sft.api.rls.ResourceListsFactory; // @ServiceAttributes(mscontrolJndiName = "mscontrol.OCMP") // @ServiceAttributes(mscontrolJndiName = "mscontrol.dlg309") @CommunicationBean public class ConfRLSBean { @Context CommunicationSession sess; @Context CommunicationContext<> ctx; @Context CommunicationService service; private Logger logger = Logger.getLogger("sft.test"); @CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION) void handleInit() { Conversation call = (Conversation) ctx.getCommunication(); String calleeName = call.getCallee().getName(); logger.info(call.getCaller().getName() + " call " + calleeName); if(calleeName.equals("conf1@example.com")) { Conference conf = sess.createConference(calleeName, call); Message msg = ctx.getMessage(); ResourceLists rlss = handleMsg(msg); if (conf.getAgent("test") == null) { conf.addAgent("test", new TestAgent(rlss)); } } } @CommunicationEvent(type = CommunicationEvent.Type.STARTED) void handleStart() { String confName = ctx.getCommunication().getName(); logger.info(confName + " is started."); } @CommunicationEvent(type = CommunicationEvent.Type.ESTABLISHED) void handleEstablished() { Communication comm = ctx.getCommunication(); logger.info(comm.getName() + " established"); Agent<ResourceLists> testAgent = comm.getAgent("test"); ResourceLists incomingRlss = testAgent.get(); System.out.println("incomingRlss size : "); List<ResourceList> resourceList = incomingRlss.getResourceList(); for (ResourceList rls: resourceList) { System.out.println(rls.getResourceListData().size()); } System.out.println(incomingRlss.toString()); ResourceListsFactory rlssFactory = service.createResourceListsFactory(); ResourceLists recipientLists = rlssFactory.createRecipientLists(incomingRlss); resourceList = recipientLists.getResourceList(); System.out.println("recipientLists size : "); for (ResourceList rls: resourceList) { System.out.println(rls.getResourceListData().size()); } System.out.println(recipientLists.toString()); ResourceLists recipientHistLists = rlssFactory.createRecipientHistoryLists(incomingRlss); System.out.println("recipientHistLists size : "); resourceList = recipientHistLists.getResourceList(); for (ResourceList rls: resourceList) { System.out.println(rls.getResourceListData().size()); } System.out.println(recipientHistLists.toString()); List<ResourceList> dataList = recipientLists.getResourceList(); for (ResourceList rls : dataList) { List<Entry> entryList = rls.getResourceListData(); for (Entry entry: entryList) { String userName = entry.getName(); UserParticipant up = sess.createParticipant(UserParticipant.class, userName); // up.setRecipientHistoryList(recipientHistLists); comm.addParticipant(up); } } } @CommunicationEvent(type = CommunicationEvent.Type.FINISHED) void handleEnd() { String confName = ctx.getCommunication().getName(); logger.info(confName + " is finished."); } // Handle ParticipantEvent @ParticipantEvent(type = ParticipantEvent.Type.JOINED) void handleJoin() { logger.info(ctx.getParticipant().getName() + " joined"); } private ResourceLists handleMsg(Message msg) { if (msg instanceof ResourceListsMessage) { ResourceLists rlss = ((ResourceListsMessage) msg).getResourceLists(); return rlss; } return null; } } class TestAgent extends Agent<ResourceLists> { private static final long serialVersionUID = 1L; TestAgent(ResourceLists rls) { super(false); super.set(rls); } }
RFC 4575 defines an event package for conferencing. The conference event package allows a user to subscribe to information relating to a conference.
The conference event package allows a user to subscribe to information relating to a conference. Within the SIP protocol, conferences are represented by URIs. These URIs identify the Focus, a SIP user agent (UA), that is responsible for ensuring that all users in the conference can communicate with each other. The Focus has sufficient information about the state of the conference to inform subscribers about it.
The following is supported for event notifications via the conference event package:
Converged Application Server only supports a SUBSCRIPTION event without body, which is the default subscription filtering policy. See RFC 4575, Section 3.2.
The default expiration time for a subscription to a conference is one hour. Once the conference ends, all subscriptions to that conference are terminated, with a reason of “no resource” as defined in RFC 3265.
The body of the conference event package notification contains a conference information document that describes the state of a conference. All subscribers and notifiers must support the application/conference-info+xml
data format. See RFC 4575, Section 3.4.
The conference information contains sensitive information. Therefore, all subscriptions should be authenticated and authorized before approval. Authorization policy is at the discretion of the administrator, as always. See RFC 4575, Section 3.5.
You can configure a conference with:
Miminum expiration time
Maximum expiration time
Default expiration time
Maximum number of participants
The configuration is done using:
The minExpirationTime
, defaultExpirationTime
, maxExpirationTime
, and maxNumOfSubscriptions
elements of the sft.xml deployment descriptor.
The @ServiceAttributes
annotation.
The times are given in seconds.
Example 23-6 illustrates event notification expiration times and the maximum number of subscribers to a conference using the conferenceEventConfig
element of the sft.xml deployment descriptor.
Example 23-6 Conference Event Expiration in SFT.XML Deployment Descriptor
<service-attributes> <conferenceEventConfig> <minExpirationTime>100</minExpirationTime> <defaultExpirationTime>1800</defaultExpirationTime> <maxExpirationTime>3600</maxExpirationTime> <maxNumOfSubscriptions>100</maxNumOfSubscriptions> </conferenceEventConfig> </service-attributes>
Example 23-7 specifies event notification expiration times and the maximum number of subscribers using the @ServiceAttributes
annotation. Use this annotation in the CommunicationBean
Java class you create to implement the conferencing application.
Example 23-7 Specifying Bandwidth Using the @ServiceAttributes Annotation
@ServiceAttributes (conferenceEventConfig = {100, 3600, 3600, 100}) @CommunicationBean public class ConferenceBean ...
To learn more about configuring event notification for conferences with the @ServiceAttributes
annotation, see the Converged Application Server API Reference.
The conference notification service allows conference-aware participants to subscribe to it, and receive notifications that contain a list of participants. Subscribers are notified when a participant joins or leaves a conference. The conference notification service also allows a user to obtain a list of current subscribers.
The conference notification service uses the @CommunicationEvent
annotation's SUBSCRIPTION and NOTIFICATION event types.
Example 23-8 illustrates the use of the SUBSCRIPTION event type in conjunction with the SubscriptionPolicy
interface to perform authorization. SubscriptionPolicy
gets the subscription information from @CommunicationContext using the ContextElement interface.
If the application sets the Policy to SubscriptionPolicy.ACCEPTED
, the subscription will be created successfully, and a SIP 200 OK response sent to the subscriber.
If the application sets the Policy to SubscriptionPolicy.FORBIDDEN
, a SIP 403 - Forbidden response is sent to the subscriber.
If the application does not change the Policy, the default value is set to SubscriptionPolicy.NONE
. In this case, if the subscriber is authenticated by Converged Application Server, the SFT application accepts the request.
Example 23-8 Handling SUBSCRIPTION Type
... @CommunicationEvent(type = CommunicationEvent.Type.SUBSCRIPTION) void handleSub() { // The communication name is the same as the resourceId String confName = ctx.getCommunication().getName(); //Get Subscription information from CommunicationContext. ContextElement SubsElement = ctx.getContextElement(SubscriptionPolicy.class); if (SubsElement != null) { SubscriptionPolicy subsPolicy = (SubscriptionPolicy)SubsElement; // Get information about the subscription for authorization. List<String> accepList = subsPolicy.getAccept(); String contentType = subsPolicy.getContentType(); Map<String, String> parameters = subsPolicy.getEventHeaderParameters(); String resourceId = subsPolicy.getResourceId(); String subscriber = subsPolicy.getSubscriber(); String eventName = subsPolicy.getEventName(); /* Perform authorization and reject alice@example.com * subscription to conf1@example.com */ if (subscriber.equals("alice@example.com")) { if (resourceId.equals("conf1@example.com")) { subsPolicy.reject(); } } } } ...
Indicates a new notification has been created. The conference application can change the notification content in this event.
Example 23-9 illustrates the use of the NOTIFICATION event type. The subscriber Alice is removed from the list of subscribers that get notifications about the conference. Alice's info is explicitly removed from the notification details sent to Bob.
Example 23-9 Handling the NOTIFICATION Type
... @CommunicationEvent(type = CommunicationEvent.Type.NOTIFICATION) void handleNotify() { // The communication name is the same as the resourceId String confName = ctx.getCommunication().getName(); // Get ConferenceResource from CommunicationContext. ContextElement element = ctx.getContextElement(ConferenceResource.class); if (element != null) { ConferenceResource confrenceRes = (ConferenceResource) element; // Get all the information about the Resource. Application can // use them to do the authorization for notifications. ConferenceInfo defaultConferenceInfo = confrenceRes.getDefaultConferenceInfo(); Collection<String> subscribers = confrenceRes.getSubscribers(); String resourceId = confrenceRes.getResourceId(); // ******************************* // An example of authorization * // ******************************* if (resourceId.equals("aConference@example.com")) { // Alice can not get notification. if (subscribers.contains("alice@example.com")) { confrenceRes.removeSubscriber("alice@example.com"); } // Bob can not get Alice's state from conference. if (subscribers.contains("alice@example.com")) { ConferenceInfo confInfoToBob = defaultConferenceInfo.clone(); Users users = confInfoToBob.getUsers(); if (users != null) { List<User> userList = users.getUserList(); ListIterator<User> it = userList.listIterator(); while (it.hasNext()) { User user = it.next(); if (user.getEntity().contains("alice@example.com")) { it.remove(); confrenceRes.setDistinctConferenceInfo( "sipp2@example.com", confInfoToBob); } } } } } } } ...