Noise Cancellation
Enable noise cancellation by installing @stream-io/audio-filters-web, powered by krisp.ai.
Best Practices
- Check
isSupportedbefore showing noise cancellation controls. - Check
isReadybefore allowing users to enable or toggle noise cancellation. - Use
useMemowhen creating theNoiseCancellationinstance to prevent recreating it on every render. - Adjust suppression level based on your use case (voice vs music content).
- Use
statusto let users know when noise cancellation was turned off because their device is overloaded. - For NextJS, use lazy loading with Pages router or
"use client"directive with App router.
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 = useMemo(
() => 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-webIntegration
Our React SDK provides utility components and hooks that make the integration smoother.
NoiseCancellationProvider- a context provider that takes anoiseCancellationinstance as a propuseNoiseCancellation()- a hook that exposes the API that controls the NoiseCancellation behavior, including support and readiness state
import { useMemo } from "react";
import { NoiseCancellation } from "@stream-io/audio-filters-web";
import {
Call,
NoiseCancellationProvider,
StreamCall,
StreamVideo,
StreamVideoClient,
useNoiseCancellation,
} from "@stream-io/video-react-sdk";
export const MyApp = () => {
let client: StreamVideoClient; // ...
let call: Call; // ...
const noiseCancellation = useMemo(() => new NoiseCancellation(), []);
return (
<StreamVideo client={client}>
<StreamCall call={call}>
<NoiseCancellationProvider noiseCancellation={noiseCancellation}>
<MyComponentTree>
<MyToggleNoiseCancellationButton />
</MyComponentTree>
</NoiseCancellationProvider>
</StreamCall>
</StreamVideo>
);
};
const MyToggleNoiseCancellationButton = () => {
// isSupported can be `true`, `false` or `undefined`
// (undefined is used while compatibility check is in progress)
const { isSupported, isReady, isEnabled, setEnabled } =
useNoiseCancellation();
return (
<button disabled={!isSupported} onClick={() => setEnabled(!isEnabled)}>
{!isReady ? <LoadingIndicator /> : "Toggle Noise Cancellation"}
</button>
);
};Controlling noise suppression level
By default, the noiseCancellation instance will apply the maximum level of noise suppression.
However, in some cases, more granular control is necessary and can be achieved like this:
const noiseCancellation = new NoiseCancellation();
noiseCancellation.setSuppressionLevel(50); // 0-100, 0 is no suppression, 100 is maximum suppression
// or, by using the hook
const { setSuppressionLevel } = useNoiseCancellation();
setSuppressionLevel(50);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.
useNoiseCancellation() exposes a status you can use to let your users know what happened:
enabled- noise cancellation is activedisabled- noise cancellation is offsuspended- noise cancellation was turned off because the device is overloaded, and it turns back on shortlyoverloaded- noise cancellation was turned off because the device is overloaded, and it stays off
const MyNoiseCancellationStatus = () => {
const { status } = useNoiseCancellation();
if (status === "suspended") {
return (
<span>Noise cancellation is paused while your device catches up</span>
);
}
if (status === "overloaded") {
return (
<span>
Noise cancellation was turned off because your device is overloaded
</span>
);
}
return null;
};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/modelsThis 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-modelsThe 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 = useMemo(
() =>
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.
Integration in NextJS
Our @stream-io/audio-filters-web runs exclusively on the client and as such, it conflicts with frameworks that are capable
of doing Server Side Rendering (as NextJS).
App router
Make sure you flag your component that creates the NoiseCancellation instance with "use client" directive:
"use client";
import { useMemo } from "react";
import { NoiseCancellation } from "@stream-io/audio-filters-web";
export const MyComponent = () => {
const noiseCancellation = useMemo(() => new NoiseCancellation(), []);
// ...
};Pages router
When using the Pages router, we need to lazy-load the plugin as described below:
import { useEffect, useRef, useState } from "react";
import { NoiseCancellationProvider } from "@stream-io/video-react-sdk";
import type { INoiseCancellation } from "@stream-io/audio-filters-web";
export const MyComponent = () => {
const [noiseCancellation, setNoiseCancellation] =
useState<INoiseCancellation>();
const loader = useRef<Promise<void>>();
useEffect(() => {
const load = (loader.current || Promise.resolve())
.then(() => import("@stream-io/audio-filters-web"))
.then(({ NoiseCancellation }) => {
setNoiseCancellation(new NoiseCancellation());
});
return () => {
loader.current = load.then(() => setNoiseCancellation(undefined));
};
}, []);
return (
<>
// ...
{noiseCancellation && (
<NoiseCancellationProvider noiseCancellation={noiseCancellation}>
<MyComponentTree />
</NoiseCancellationProvider>
)}
</>
);
};