Renegotiating SDP Messages

At times, you may want to change the properties of a connection that has already been established. For example, you may want to "put a call on hold". In the context of SDP, "putting a call on hold" really means modifying the direction. However, one participant cannot unilaterally change the properties of the connection. They must send a new SDP offer to the remote participant, who must reply in turn with a new SDP answer. This process is known as SDP renegotiation.

Switching a Stream's Direction

The direction of a stream is what indicates whether it is one-way or two-way. There are four possible directions, which are provided by the FM.IceLink.StreamDirection enumeration:

  • SendReceive: This stream both sends and receives data.
  • SendOnly: This stream only sends data.
  • ReceiveOnly: This stream only receives data.
  • Inactive: This stream does not send or receive any data.

In the scenario mentioned above, a normal call where both parties are talking would have audio and video streams that are SendReceive. If one party wants to place the call on hold, the streams would be set to Inactive. To change the direction of an active stream, you invoke the ChangeDirection method of an AudioStream or VideoStream instance. The following code shows how to change a two-way stream so that it is inactive.

Make sure that you only change the direction of audio and video streams. You cannot change the direction of a data stream. If you attempt to, then the IceLink SDK will raise an error and refuse to process the direction change.


var videoStream = new FM.IceLink.VideoStream(localMedia, remoteMedia);
videoStream.ChangeDirection(FM.IceLink.StreamDirection.Inactive);
fm.icelink.VideoStream videoStream = new fm.icelink.VideoSteam(localMedia, remoteMedia);
videoStream.changeDirection(fm.icelink.StreamDirection.Inactive);
FMIceLinkVideoStream* videoStream = [FMIceLinkVideoStream videoStreamWithLocalMedia:localMedia remoteMedia:remoteMedia];
[videoStream changeDirectionWithNewDirection:FMIceLinkStreamDirectionInactive];
var videoStream = FMIceLinkVideoStream(localMedia: localMedia, remoteMedia: remoteMedia)
videoStream.changeDirection(FMIceLinkStreamDirectionInactive);
var videoStream = new fm.icelink.VideoStream(localMedia, remoteMedia);
videoStream.changeDirection(fm.icelink.StreamDirection.Inactive);


Note that the initial configuration of the stream affects which directions that you can specify. For example, if you did not specify a RemoteMedia instance when you initialized your streams, you can never change the direction to ReceiveOnly or SendReceive, because there is no remote media instance to receive from. Likewise, if you did not specify a LocalCameraMedia instance, you can never change the direction to SendOnly or SendReceive, because there is no local media instance to send with.

As mentioned in the introduction to this section, it is not enough to change the direction like this. You must also send a new offer to the remote party. The next section shows you how to accomplish this.

Performing Other Operations

Before you get into the details of actually performing the renegotiation, note that the only property that IceLink supports renegotiation for is for the direction of the streams. It may be possible to modify the SDP messages directly and perform a renegotiation, but this is not supported and will likely have unintended side effects.

Renegotiating With the WebSync Extension

If you are using the WebSync extension, there is an extension method that specifically handles this for you. To renegotiate the properties of a Connection instance, invoke the Renegotiate method of your Client instance and pass in the connection object. You must specify the channel id for your session and you must do this after you make any changes to the properties of the connection.

The renegotiation is not instant, and returns a promise that resolves when the renegotiation is complete. You should always monitor this promise to ensure that the renegotiation was successful. If, for whatever reason, the new offer is not accepted by the remote party, then the connection will return to its previous state. The following examples show the use of the Renegotiate method.


Client.Renegotiate("/123456", connection).Then(result =>
{
    Console.WriteLine("renegotiation complete");
}).Fail(ex =>
{
    Console.WriteLine("failed to renegotiate");
});
client.renegotiate("/123456", connection).then(result -> {
    System.out.println("renegotiation complete");
}).fail(ex -> {
    System.out.println("failed to renegotiate");
});
[[[client renegotiateWithConferenceChannel:@"/123456" connection:connection] thenWithResolveActionBlock: ^(NSObject* result) {
    NSLog(@"renegotiation complete");
}] failWithRejectActionBlock: ^(NSException* ex) {
    NSLog(@"failed to renegotiate");
}];
client.renegotiate(conferenceChannel: "/123456", connection: connection).then(resolveActionBlock: { (result:NSObject) in
    print("renegotiation complete")
}).fail(rejectActionBlock: { (ex:NSException) in
    print("failed to renegotiate")
})
client.renegotiate("/123456", connection).then(function(result) {
    console.log("renegotiation complete");
}).fail(function(ex) {
    console.log("failed to renegotiate");
});


If you make multiple renegotiation requests at the same time, they will be queued and processed in the order that they are received. This can cause unpredictable results if one or more of the requests are rejected by the remote party. For this reason, you should try to only perform one renegotiation at a time.

The above information covers what happens when a local party initiates a renegotiation, but it is equally possible that a remote party is the one who initiates. IceLink will automatically respond to the the renegotiation request, so there is no API call that you need to invoke. However, you may wish to be notified of this attempt, so that you can avoid sending a competing renegotiation offer. To do this, add an OnSignallingChange event handler to your Connection instance. This event is raised whenever the signalling state of a connection is changed. You can view the signalling state of a connection by accessing its SignallingState property, of which there are four distinct values:

  • New: no offer has been sent or received
  • HasLocalOffer: a local offer has been sent but no remote answer has been received
  • HasRemoteOffer: a remote offer has been received but no local answer has been sent
  • Stable: the offer/answer exchange is complete

An example is shown below that demonstrates how to be notified when a new renegotiation request is received.


connection.OnSignallingStateChange += (FM.IceLink.Connection c) =>
{
    if (c.SignallingState == FM.IceLink.SignallingState.HasRemoteOffer)
    {
        Console.WriteLine("renegotiation request received");
    }
};
connection.addOnSignallingStateChange((fm.icelink.Connection c) -> {
    if (c.getSignallingState() == fm.icelink.SignallingState.HasRemoteOffer) {
        System.out.println("renegotiation request received");
    }
});
[connection addOnSignallingStateChange: ^(FMIceLinkConnection* c) {
    if ([c signallingState] == FMIceLinkSignallingStateHasRemoteOffer) {
        NSLog(@"renegotiation request received");
    }
}];
connection.addOnSignallingStateChange { (c:FMIceLinkConnection) in
    if c.signallingState() == FMIceLinkSignallingStateHasRemoteOffer {
        print("renegotiation request received")
    }
}
connection.addOnSignallingStateChange(function(c) {
    if (c.getSignallingState() == fm.icelink.SignallingState.HasRemoteOffer) {
        console.log("renegotation request received");
    }
});


Wrapping Up

You can now successfully renegotiate the direction of the streams in a connection instance, which is useful for putting a session on hold and for other scenarios. If you need a refresher on how the WebSync extension works, return to the section on Connecting to the Signalling Server.