Noise Cancellation

Noise Cancellation capabilities of our Plain-JS Video SDK can be enabled by installing our @stream-io/audio-filters-web plugin. Under the hood, this package uses the technology developed by krisp.ai.

Compatibility

This plugin currently supports only modern desktop browsers:

  • Chrome, Firefox and Edge
  • Safari 17.4.1+ (opt-in)

Noise cancellation isn't fully reliable in Safari yet, so by default it reports Safari and other WebKit-based browsers (every iOS browser and in-app WebView) as unsupported. You can opt in with the withSafariSupport option:

const noiseCancellation = new NoiseCancellation({ withSafariSupport: true });

Install the plugin

With your favourite package manager, run the following command:

yarn add @stream-io/audio-filters-web
# or
npm install @stream-io/audio-filters-web

Integration

In the following code snippet, we show how to check if а platform is supported, initialize the plugin, and enable/disable it.

import { NoiseCancellation } from "@stream-io/audio-filters-web";

const call = client.call(type, id);
await call.get(); // or call.getOrCreate()

const noiseCancellation = new NoiseCancellation();
const isSupported = await noiseCancellation.isSupported();
if (isSupported) {
  await noiseCancellation.init();
  await call.microphone.enableNoiseCancellation(noiseCancellation);
}

// disable it and unregister the plugin
await call.microphone.disableNoiseCancellation();

NoiseCancellation API

The NoiseCancellation class exposes the following methods:

// creates a new NoiseCancellation instance
const noiseCancellation = new NoiseCancellation();

// returns true for supported platforms
await noiseCancellation.isSupported();

// initializes the plugin
await noiseCancellation.init();

// will temporarily disable NC (doesn't unregister the plugin)
noiseCancellation.disable();

// will enable NC (requires the plugin to be registered first)
noiseCancellation.enable();

// returns true if NC is enabled
const isEnabled = await noiseCancellation.isEnabled();

// sets the suppression level [0, 100]
// where 0 is no suppression and 100 is maximum suppression
// the default value is 100
noiseCancellation.setSuppressionLevel(50);

// listen for state updates
const off = noiseCancellation.on("change", (v) => {
  console.log(`Noise Cancellation is ${v ? "enabled" : "disabled"}`);
});

// listen for automatic shutdowns caused by device overload
const offOverload = noiseCancellation.on("overload", ({ willRestore }) => {
  console.log(
    willRestore
      ? "Noise Cancellation is paused and will resume shortly"
      : "Noise Cancellation was turned off",
  );
});

Handling device overload

Noise cancellation is CPU intensive. When the device can't keep up, the filter turns itself off for a moment and then tries again. If the device stays overloaded, noise cancellation stays off until it's turned on again.

The call's microphone exposes a noiseCancellationStatus$ observable you can use to let your users know what happened:

  • enabled - noise cancellation is active
  • disabled - noise cancellation is off
  • suspended - noise cancellation was turned off because the device is overloaded, and it turns back on shortly
  • overloaded - noise cancellation was turned off because the device is overloaded, and it stays off
const subscription = call.microphone.state.noiseCancellationStatus$.subscribe(
  (status) => {
    if (status === "suspended" || status === "overloaded") {
      showWarning(
        "Noise cancellation was turned off because your device is overloaded",
      );
    }
  },
);

// the current value is also available synchronously
const status = call.microphone.state.noiseCancellationStatus;

You can tune how long to wait before turning noise cancellation back on, and how many times to try:

const noiseCancellation = new NoiseCancellation({
  restoreTimeoutMs: 15000,
  restoreAttempts: 3,
});

Self-hosting model assets (CSP compatibility)

By default, @stream-io/audio-filters-web loads its noise-cancellation model at runtime from the unpkg.com CDN. The default base path resolves to:

https://unpkg.com/@stream-io/audio-filters-web@<version>/src/krispai/models

This works out of the box, but it can be a problem if your application enforces a strict Content Security Policy (CSP). You would have to allowlist unpkg.com in your connect-src directive, or - preferably - avoid the third-party dependency altogether by hosting the model on your own domain and pointing the plugin to it.

Step 1 - Copy the model into your app

The model ships inside the @stream-io/audio-filters-web package, in its src/krispai/models folder. Copy it into your app's public/static assets directory:

rm -rf public/nc-models
cp -R node_modules/@stream-io/audio-filters-web/src/krispai/models public/nc-models

The rm -rf keeps the command idempotent: without it, re-running cp -R nests the source under the existing folder (public/nc-models/models/...) and leaves stale files behind.

Step 2 - Override basePath

Pass a basePath (pointing to the directory that serves the copied model files) when creating the NoiseCancellation instance. Note that the public/ directory is served from the web root, so files in public/nc-models are exposed at /nc-models (no /public prefix):

const noiseCancellation = new NoiseCancellation({
  basePath: "https://my.domain.com/nc-models",
});

A same-origin path works too (e.g. basePath: "/nc-models").

Keep the model in sync with the SDK

The hosted model must match the installed SDK version. To prevent version drift, copy it automatically on install with a postinstall script (or a step in your build) in your package.json.

{
  "scripts": {
    "postinstall": "node -e \"require('fs').cpSync('node_modules/@stream-io/audio-filters-web/src/krispai/models', 'public/nc-models', { recursive: true })\""
  }
}

Once the model is self-hosted and basePath is set, the app no longer makes requests to unpkg.com, so you can remove it from your CSP allowlist.