Photo of Marco
Marco
  • Wed Jan 29 2025

Contributing Priority Encodings To JsSIP

Contributing Priority Encodings To JsSIP

Today we’re telling you about our recent experience of contributing a feature to the JsSIP project, and how we discovered a Safari bug in the process.

What are priority encodings?

When working with VOIP, and especially when developing a VOIP application, there are some lesser known tools that we can use to tailor the application to our specific needs, and to make sure it performs as well as it can for its particular use case.

One of these tools is the option to set the sender's priority on media stream tracks that are added to an RTC peer connection. What does this do? It lets you set a header on the RTP packets which assigns them a different (usually higher) priority, relative to the other packets. The levels are very-low, low, medium and high, with low being the default value. You can read the full spec here, if you’re interested.

By setting a high priority to the stream we’re most interested in, while keeping the less critical ones low, we can make sure that resources are applied as efficiently as possible. In most cases, this might not make a huge difference. In some applications, however, this could be used as a QoS marker, to make sure packets are given priority by the elements of the network as they make their way to their destination.

We found the need for priority encodings while working on a VOIP application based on the JsSIP module. As it turns out, JsSIP did not support setting priority encodings, so we set out to add it ourselves!

Building the feature into JsSIP

We like to use the patch-package module to make changes to our dependencies. It lets you make your changes to the code in the node_modules folder directly, and then creates a patch that can be reapplied whenever you reinstall your dependencies. It’s a quick way to test what works and to see the results directly in the code that uses that dependency, before forking the original project and opening a PR on it.

Setting the encoders on the RTCRtpSender in the JsSIP codebase is pretty straightforward:

mediaStream.getTracks().forEach((track) => {
  let sender = peerconnection.addTrack(track, stream);
  const params = sender.getParameters();
  params.encodings = params.encodings.map((e) => {
    if (e.priority) {
      e.priority = 'high';
    }
    if (e.networkPriority) {
      e.networkPriority = 'high';
    }
    return e;
  });
  sender.setParameters(params);
});

In modern WebRTC applications the sender will likely be already available, so its parameters could be set directly without looping through the mediastream tracks.

Once we had the code working in our project, we replicated the changes in our JsSIP fork. Before opening a PR, it’s good practice to test the changes to a module in a separate, simplified app, to make sure everything still works as expected. We used this one, and connected it to our own Asterisk server for some echo tests.

Checking that everything works

As mentioned above, what setParameters() does is set the headers in the RTP packets. There are two ways in which we can check that the encodings were set as expected.

getParameters()

Firstly, we can get the sender parameters from the track event in the RTC Session:

const session = jssip.call(uri, SESSION_OPTIONS);
session.connection.addEventListener('track', handleTrackEvent);

const handleTrackEvent = (event) => {
  const pc = event.currentTarget;
  const senders = pc.getSenders();
  const params = senders[0].getParameters();
  console.log('peer conn params', params);
});

This will show you that Javascript will apply packets priority, as you can see here:

Screenshot of the Google Chrome console log showing the sender’s parameters of priority and network priority are set to “high”.
Screenshot of the Google Chrome console log showing the sender’s parameters of priority and network priority are set to “high”.

More precisely, this shows that Javascript intends to apply packets priority… To make sure it does, you need to go have a look inside the packets.

Inspecting packets

We can go one level deeper and take a look at the packets that are actually being sent, and double check their headers. We do this with Wireshark.

Wireshark inspects all the packets being sent and received by your network interface, and lets you see their content. Let’s do a test call and have a look.

From my simple test app running my patched version of JsSIP, I call “echo” on our VOIP server. When the call is established, I select Wi-Fi on Wireshark and start a new recording. A few seconds’ worth of recording is plenty, so feel free to stop soon. Now let’s have a look at some packets: There are a lot, and I found that the protocol column might not always be labelled reliably. Ideally you want to filter the packets going from your machine’s IP address and with a protocol of RTP (_ws.col.protocol == "RTP" && ip.src == 192.168.xx.xx), but personally I found that just looking at the Source and Destination IP addresses might be more reliable. 

Once you find some packets from your test call, open the inspector and go to Internet Protocol v4 > Differentiated Services Field. If your packet’s priority was set to high correctly, you’ll see Differentiated Services Codepoint: Expedited Forwarding (46). Like so:

A screenshot of the Wireshark packet inspector shows that the packet’s header is set to Expedited Forwarding (46).
A screenshot of the Wireshark packet inspector shows that the packet’s header is set to Expedited Forwarding (46).

Testing on Safari

According to MDN, priority encodings are fully supported on Safari. Well, that’s only partially true! Yes, the JS API in Safari will let you set the encodings. You can check that by calling getParameters() as we did above, and you’ll see this:

Screenshot of the Safari console log showing the sender’s parameters of priority and network priority are set to “high”.
Screenshot of the Safari console log showing the sender’s parameters of priority and network priority are set to “high”.

However, if you then go and inspect the actual packets, you’ll see that this instruction is ignored by WebKit, and the packets are sent with default priority headers:

A screenshot of the Wireshark packet inspector shows that the packet’s header is set to Default (0).
A screenshot of the Wireshark packet inspector shows that the packet’s header is set to Default (0).

As this appears to be a bug in WebKit, there’s not much we can do about it at this point. We reported the bug to the WebKit bug tracker; meanwhile, keep in mind that you can’t effectively set priority encodings on Safari, and your app will simply work with the defaults.

Conclusion

I hope this post gave you some insight into how WebRTC offers tools to tailor your app and optimise it to its specific needs. This was also a good chance for me to get more into analysing packets, which is something I hadn’t had much experience with in the past.

Once again, Safari proves to be the Internet Explorer of WebRTC… At least in this case, the same app will still work, just a little worse than on a Chromium browser! How’s that for progressive enhancement?

Need help?

If you’d like some help developing or optimising your VOIP app, we’re happy to help. Nimble Ape has been consulting on real-time media projects for a decade, building products for clients big and small all over the globe.

Why not drop us a line on contact@nimblea.pe.

JsSIPVOIPWebRTCOpen SourcePriority encodingsQoS markersWiresharkChromeSafariWebKit