Introduction Last updated: 2021-06-11

Vectorly's client-side SDK makes it easy to integrate AI filters, such as Background Filters (virtual backgrounds, background blur) as well as AI Upscaling, into WebRTC streaming applications


When you sign up, you'll get a token which, you will need to use the library. Next, you can install the ai-filters library via NPM or via CDN


npm install --save @vectorly-io/ai-filters


Available Filters

We've compiled a set of AI filters from research & Academia, open source projects as well as our own custom AI filters, all of which are able to be accessed through the same Vectorly interface.

Background Filter

By using the Background Filter, you can implement features like Virtual Backgrounds or Background Blur, to give users additional privacy when calling from home. The AI model used for running background segmentation is the meet model, used by Google Meet.

Background Filters example:
Original Video Stream
Background Blur
Virtual Background

See the Background Filters section for more details

Upscaling Filter

Vectorly has built it's own AI Upscaling filter based on a technique called Super Resolution, which uses AI to upscale and enhance images. Through Super Resolution, we can upscale and clean-up low-resolution video, making it look close to HD quality.

Super Resolution Example:
240p upscaled to 720p
Original 720p

With AI Upscaling, you can improve the clarity & quality of video streams when the source resolution is of low quality.

You can also stream SD content to users and upscale it to HD in real time as they're watching it, providing an HD viewing experience while only consuming the bandwidth for the low-resolution video (50 to 90% less data than for the HD video).

See the AI Upscaling section for more details

Audio Denoise

In Development

Video Denoise

In Development

Lighting Correction

In Development

Background Filters

Our Background filters, based on the Google Meet background segmentaiton model, make it easy to implemet background blur and virtual background features

You can find a live demo of our virtual background filter here


The basic API for loading the background filter via NPM is:

     import { BackgroundFilter } from '@vectorly-io/ai-filters';

For loading via CDN, you can access the background filter from the vectorly object

     const BackgroundFilter = vectorly.BackgroundFilter;

You can find more detailed loading instructions on the API reference page

Basic Usage

Vectorly's Background Filter takes as an input any MediaStream or MediaStreamTrack element, so for a WebRTC application, all you need to do is to instantiate the filter object with the MediaStream or MediaStreamTrack element you want to filter. The output is a MediaStreamTrack, which can be sent via WebRTC or loaded locally into a video element

The basic API for loading the background filter is:

   const stream = await navigator.mediaDevices.getUserMedia({video:true, audio:true});
   const filter = new BackgroundFilter(stream, {token: 'vectorly-token', background: 'blur'});
   const outputStream =  await filter.getOutput();

The above example is for a Background Blur filter. For virtual backgrounds, where you replace the background with an image, the API is:

   const filter = new BackgroundFilter(stream, {token: 'vectorly-token', background: 'my-image-url.png'});

You can find a full set of methods and parameters on the API reference page

Browser support

Our background filters are supported on all major browsers, except for internet explorer. See a table of browser support below

Chrome Safari Firefox Edge Opera IE
Supported Yes Yes Yes Yes Yes No
SIMD acceleration Yes, since 91 No Yes, since 89 Yes, since 84 Yes, since 77 No
Offscreen Support Yes No No Yes Yes No

Offscreen Support
Offscreen support uses OffscreenCanvas to run video-processing workloads in a worker. This both has some performance benefits and is also necessary to continue running the AI filter when the current tab is hidden or minimized.


For browsers that do not have Offscreen Support, the filtered stream will pause while the user's tab is hidden / minimized, and will resume again when the user's tab is active again..

SIMD acceleration

Recent versions of Chrome, Firefox, Edge and Opera support SIMD acceleration since June 2021. SIMD acceleration enables much higher framerates and low CPU overhead. SIMD acceleration is enabled automatically by default, if it is supported by the browser.


Integrating the filter with any specific Video Conferencing API or service just requires finding the MediaStream element associated with video stream you want to filter. The following sub-sections discuss how to integrate the filter with various conferencing services.

Vanilla WebRTC

As shown above, the API for basic/general WebRTC is:

   const videoTrack = await navigator.mediaDevices.getUserMedia({video:true, audio:true}).getVideoTracks()[0];
   const filter = new BackgroundFilter(videoTrack, { background: 'image.jpg'});
   const virtualBackgroundTrack = await filter.getOutputTrack();

You can find a demo repository for vanilla WebRTC background filtering here


You can enable filters on any VideoTrack object by feeding into the Filter's Jitsi Plugin (see reference).

   const room = connection.initJitsiConference('conference', confOptions);

   JitsiMeetJS.createLocalTracks({devices: ['video']}).then(tracks => {
      const filter = new BackgroundFilter(tracks[0], {token: 'insert-vectorly-token', background: 'blur'});




For Web deployments using Agora (specifically the 4.x API), you can just feed the video track to the Background filter, which will return a filtered video track which you can publish.

    let videoTrack = AgoraRTC.createCameraVideoTrack();
    let audioTrack = AgoraRTC.createMicrophoneAudioTrack();

    const filter = new BackgroundFilter(videoTrack._mediaStreamTrack, {token: 'insert-vectorly-token-here', background: 'blur'});
    filter.getOutputTracj().then(function(filteredTrack ){

       const filteredAgoraTrack = AgoraRTC.createCustomVideoTrack({
         mediaStreamTrack: filteredTrack
      client.publish([filteredAgoraTrack, audioTrack]);


You can find a working demo repo here


You can enable filters on any VideoTrack object by extracting the raw MediaStreamTrack, running the filter on that, and creating a new LocalVideoTrack.

   const Twilio = require('twilio-video');

   const localVideoTrack = await Twilio.createLocalVideoTrack();
   const filter = new BackgroundFilter(videoTrack.mediaStreamTrack, {token: 'insert-vectorly-token', background: 'blur'});
   const outputTrack = await filter.getOutputTrack();
   const filteredTrack = new Twilio.LocalVideoTrack(outputTrack);


If you're building a custom UI with, you can use the callObject's setInputDevices method to set the filtered video track as the upload stream.

   const sourceVideoTrack = callObject._participants.local.videoTrack;

   const filter = new vectorly.BackgroundFilter(sourceVideoTrack, {token: 'your-vectorly-token', background: ''});

   filter.getOutputTrack().then(function(filteredTrack ){

         videoSource: filteredTrack


You can find a working example repo here

Vonage / OpenTok

When you create a Publisher object, just feed the filtered video Track as the video Source

   const stream = await navigator.mediaDevices.getUserMedia({video:true, audio:true});
   const filter = new BackgroundFilter(stream, {token: 'vectorly-token', background: 'blur'});
   const outputTrack =  await filter.getOutpuTrack();

   var publisher = OT.initPublisher('publisher', {
      insertMode: 'append',
      width: '100%',
      height: '100%',
      videoSource: outputTrack
   }, handleError);

You can find a working demo repo here

Starter Backgrounds

For convenience, if you're interested in adding a virtual backgrounds feature and need some starter images, here are a few:


Once you have instantiated the filter object, you can access basic filter events, like onload and error handling.

   const filter = new BackgroundFilter(stream, config);

   filter.on('load', function () {
     console.log("filter initialized");

   filter.on('start', function () {
      console.log("Starting filter");

   filter.on('stop', function () {
      console.log("Stopping filter");

   filter.on('error', function () {
     console.log("Filter failed to initialize");

If the filter fails to load, then it will pass through the original video stream


You can enable and disable the filter programatically.

   const filter = new BackgroundFilter(video, config);



You can also change the inputs to the background filter dynamically

    //Change the background image, or set to "blur" to set a background blur
   await filter.changeBackground("new-background-image.png");

   //Change the source media stream
   const devices = await navigator.mediaDevices.enumerateDevices();
   const alternateWebCam = devices[1]; //Just an example, don't literally copy/paste this
   const alternateWebCamStream  = navigator.getUserMedia({video: {deviceId: alternateWebCam.deviceId}});
   await filter.changeInput(alternateWebCamStream);


You can also set the blur level (on a scale of 1 to 10) on initialization, or dynamically with changeBlurRadius method


   const filter = new BackgroundFilter(stream, {token: 'vectorly-token', background: 'blur', blurRadius: 5});
   filter.changeBlurRadius(3);  // 3/10 is less blurry than 5/10


You can see a full set of available methods in the API documentation

AI Upscaling


The API for loading the Upscaler is


     import vectorlyUpscaler from @vectorly-io/ai-upscaler;


For loading via CDN, you can access the upscaling filter as the vectorlyUpscaler object, which will be available in the global scope

For web environments, we've packaged our upscaler as a standalone Javascript library, as well as as plugins to several popular HTML5 video players (see the full API for more detail).

Basic usage

For the vectorlyUpscaler, the basic API involves instantiating an vectorlyUpscaler object, and specifying a video element.

   const video = document.getElementById("video");

   const config = {
	   token: '...'

   const upscaler = new vectorlyUpscaler(video, config);

This automatically upscales the video, by overlaying a canvas element with the upscaled video frames on top of the video element. When the video plays, the upscaler will automatically upscale each frame and update the canvas element. See the styling section for more detail.

Browser support

Our upscaling filters are supported on all major browsers, except for internet explorer. See a table of browser support below

Chrome Safari Firefox Edge Opera IE
Supported Yes Yes Yes Yes Yes No


General WebRTC

The vectorlyUpscaler works with any video tag, so for a WebRTC application, all you need to do is to instantiate the upscaler object with the video element you want to upscale.

   const upscaler  = new vectorlyUpscaler(document.getElementById("remoteVideo"), {token: 'insert-vectorly-token-here'});

We have an example repository, showing how Vectorly can be integrated with WebRTC, as well as a full working general WebRTC demo here.

Integrating the upscaler with any specific Video Conferencing API or service just requires finding the video element associated with video stream you want to upscale.


You can enable upscaling on any VideoTrack object by intercepting the corresponding video element you attach it to (see reference).

   const room = connection.initJitsiConference('conference', confOptions);
   room.on(, function(track){

      const videoElement = document.createElement('video');

      const upscaler = new vectorlyUpscaler(videoElement.current, {token: 'insert-vectorly-token'});



For Web deployments using Agora, you can find the video element of the stream you want to upscale by using the stream's ID.

    let stream = AgoraRTC.createStream({
        streamID: uid,
        audio: true,
        video: true,
        screen: false

    stream.init(function() {'target-div');
        const video = document.getElementById("video" + stream.getId());
        const upscaler = new vectorlyUpscaler(video, {token: 'insert-vectorly-token-here'});




You can enable upscaling on any VideoTrack object by intercepting the corresponding video element you attach it to (see reference).

If you use the track.attach() method to create a video element:

   const Video = require('twilio-video');

   Video.createLocalVideoTrack().then(function(videoTrack) {
     const videoElement = videoTrack.attach();
     const upscaler = new vectorlyUpscaler(videoElement.current, {token: 'insert-vectorly-token'});

If you specify your own video element:

   const Video = require('twilio-video');

   const videoElement = document.createElement('video');

   Video.createLocalVideoTrack().then(function(videoTrack) {
     const upscaler = new vectorlyUpscaler(videoEl.current, {token: 'insert-vectorly-token'});

OpenTok / Vonage

When a Subscriber or Publisher creates a video element you can intercept it and feed that video to the Vectorly Upscaler.

You can use the subscriber.element property to intercept the video element

    session.on('streamCreated', function(event) {

        const subscriber = session.subscribe(, 'subscriber', {
            insertMode: 'append',
            width: '100%',
            height: '100%'
        }, handleError);

        subscriber.on('videoElementCreated', function (){
            const video = subscriber.element.querySelector('video');
            const upscaler  = new vectorlyUpscaler(video, {token: '..your-token....'});



This also works with a publisher object. Refer to the Vonage documentation for styling - Vectorly's upscaler will fit within the styling defined by OpenTok.

See our example repistory for a working code example

You can integrate Vectorly's AI upscaler with if you're building a custom custom video chat interface . Using the default React code sample from Daily, we've built a full working demo reference

   useEffect(() => {
	videoEl.current &&
	(videoEl.current.srcObject = new MediaStream([videoTrack]));
	if (videoEl.current && props.isLarge) {
	window.upscalers = window.upscalers || {}
	window.upscalers[] = new vectorlyUpscaler(videoEl.current, {token: 'insert-vectorly-token'});
   }, [videoTrack]);

You just need to make sure you intercept the video element associated with the video track you want to upscale.

Vectorly's AI upscaler is not compatible with the pre-built UI from, as the pre-built UI is loaded via iframe, making it impossible to access the video element through a third party application.


If you're building an electron app, the Vectorly library is fairly plug and play, and will work with either CDN or NPM installation.

You can see a demo electron app repostory here


Besides the vectorlyUpscaler module, we have several plugins for specific HTML5 players. (see the full API for more detail).


   const player = videojs('my-video')
   videojs.registerPlugin('vectorlyPlugin', vectorlyUpscaler.videoJSPlugin);
   const vjsUpscaler = player.vectorlyPlugin({token: '...'})

Shaka player

    async function init() {
       const video = document.getElementById('my-video');
       const ui = video['ui'];
       const controls = ui.getControls();
       const player = controls.getPlayer();

       try {
           await player.load(url);
           // This runs if the asynchronous load is successful.
           const upscaler = new vectorlyUpscaler.shakaPlugin(player,{
                token: '...'
       } catch (error) {

    document.addEventListener('shaka-ui-loaded', init);

Custom plugin

You can easily build a plugin for Vectorly for any HTML5 video player. All you really need is the video tag, and the video container div, which contains the video UI elements and which is used for styling and layout. See a demo plugin code, off of which our other HMTL5 Player plugins are based
   import vectorlyUpscaler from '@vectorly-io/ai-upscaler';

   class myPlugin {

     constructor(videoElement, config){

       const container = videoElement.parentNode; // Or whatever the video container div is
       const upscaler = new vectorlyUpscaler(videoElement, config);
       this.upscaler = upscaler;


     on(event, callback){
       this.upscaler.on(event, callback)



     changeNetwork(networkParams) {

   export default myPlugin


Once you have instantiated the upscaler object, you can access basic upscaler events, like onload and error handling.

   const upscaler = new vectorlyUpscaler(video, config);

   upscaler.on('load', function () {
     console.log("Upscaler initialized");

   upscaler.on('start', function () {
      console.log("Starting upscaling");

   upscaler.on('stop', function () {
      console.log("Stopping upscaling");

   upscaler.on('error', function () {
     console.log("Failed to initialize");


You can also enable and disable the upscaler programatically.

   const upscaler = new vectorlyUpscaler(video, config);



Styling and Scaling

Let's say you have a video element, inside of a basic container div.

   <div id="container">
      <video src="video.mp4" ></video>

When you feed that video element to the Upcaler instantiation function, it will create a canvas element as a sibling node, with the same parent node as the video element.

     <div id="container">
         <video src="video.mp4"  style="visibility: hidden"></video>
         <canvas  id="output" ></canvas>  // Where the upscaled frames are drawn
The upscaler library styles this canvas to occupy 100% of the width and height of the parent element, which in practice, covers the video element in most HTML5 video player interfaces.

To have more control over the styling and position of the output, you can use the containeroption, to specify a div element to place the destination canvas.

   const video = document.getElementById("video");
   const div = document.getElementById("my-div");

   const config = {
	   token: '...',
	   container:  div //Any div element,

   const upscaler = new vectorlyUpscaler(video, config);
The output canvas will occupy the exact dimensions of the container div, and will dynamically resize and re-position whenever the container div is moved, resized or changed. To dynamically style and position the output therefore, you should style and position the container element.


There are multiple AI models you can choose from. The default is 'residual_3k_3x', but you can specify a model when instantiating the upscaler object

   const upscaler = new vectorlyUpscaler(video, {token: '...', networkParams: { name: 'residual_3k_3x', tag: 'general', version: '2.1'}});
We are constantly releasing new models. You can find a comprehensive list of models here

Low level controls

For use cases where lower level control is needed, such as upscaling indidual frames or images, using a custom decoder or upscaling as part of a broader image processing pipeline, you can use the vectorly-core library.

With the low level upscaling API, you have control over

  • The Input source
  • The destination
  • When rendering happens

Setting a destination

Each Upscaler object is tied to an individual canvas element, and renders to that canvas element.

You specify the canvas element you want to render the upscales to via the upscaler constructor

   const upscaler = new vectorlyUpscaler.core();
   upscaler.load({w: videoWidth,
	          h: videoHeight,
	          renderSize: {w: videoWidth*2, h: videoHeight*2},
	          canvas: document.getElementById('your-canvas-element'),
	          networkParams : {name: 'model-name', tag: 'model-tag', version: 'model-version'},
	          token: "your-token"});

If you want to upscale multiple streams to different canvases, you will need to define a seperate upscaler for each canvas element.

Setting an input

At any time, you can set the input of the upscaler via the upscaler.setInput() method

 upscaler.setInput(source); // Sets input element

Accepted sources include

  • HTMLImageElement
  • HTMLCanvasElement
  • HTMLVideoElement
  • ImageData
  • ImageBitmap
  • Anything else that the texImage2d function accepts


Finally, you can render using


Which will run the AI upscaling process on the canvas

Styling & Scaling

You need to set the input width and height of your input image or video streaming using the w and h properties in the constructor.

Based on whether you are using a 2X network, or 3X network, it will set the canvas.width and canvas.height property to 2x or 3x the specified w and h.

If you want your canvas to be displayed at anything other than 2*wby 2*h on the screen, you should use CSS styling. = desiredWidth + "px"; = desiredheight + "px";

The browser will still upscale the image from wxh to 2*w x 2*h, but will then use CSS styling & scaling (bicubic scaling) to scale the final output to the height/width you specify via CSS.



Our Android SDK works as a plugin to ExoPlayer. You will therefore need to use ExoPlayer, or an ExoPlayer derived player, in order upscale video.

First, you'll need to include our SDK into your app's gradle file. You can import it from our Maven repository, as shown below.

   repositories {
     maven { url "" }
   dependencies {
      implementation 'io.vectorly.glnnrender:glnnrender:0.1.1'

You can then add the following imports into the activity which manages your ExoPlayer view

   import io.vectorly.glnnrender.GlPlayerView;
   import io.vectorly.glnnrender.networks.NetworkTypes;

Once the ExoPlayer view is set up, you can call set up the Upscaler as in the following example. You'll need to feed your API key, which you can get from the Vectorly dashboard.

   private GlPlayerView ePlayerView;

   private void setupUpscalerView() {

           String api_key = "...";
           ePlayerView = new GlPlayerView(this, api_key);

           ePlayerView.setNetwork(NetworkTypes.DEFAULT, getApplicationContext());
            ePlayerView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
           ((MovieWrapperView) findViewById(;



We've created a full working example using our library, which you can find below

Example Repo


We plant to release an iOS SDK in Summer 2021


When running AI filters on client-devices, the most practical challenge is client side performance, as it requires doing large numbers of computations. This can especially become an issue when dealing with low-end devices (such as entry-level smartphones).

Accordingly, we have focused a great deal on making our AI models as efficient as possible, to enable good quality outputs while still maintaining good client-side rendering performance on low-end devices.

Background Segmentation

Our background filter is based on the Google Selfie model, however it implemented and deployed with WebGL-based optimizations to use less CPU than Google Meet itself or other background SDKs, especially on the main thread.

You can verify performance for yourself with a profiling tool like Google Chrome's performance profiler

You can measure the fps on any given device by adding the analyticsEnabled flag as true in the configuration parameters.

  const config = {
	   token: '...',
	   background: 'blur',
	   analyticsEnabled: true

   const filter = BackgroundFilter(video, config);


Setting analyticsEnabled to true will also load analytics.js, which itself loads a version of Sentry.js, and sends anonymous performance data back to our servers.

Performance Considerations

We recommend only running the Web Background filters library for desktop users. The performance is considerably worse on Mobile because because the overhead of communicating between the browser and GPU is much higher on mobile devices.

We expect the performance to rival or exceed desktop clients with native mobile SDKs, as is the case for our AI Upscaler Android SDK. We are planning to develop a Background Filters Android and iOS SDK in Q3 2021.

AI Upscaling

The primary "cost" to doing super-resolution is computational complexity. While we have put a lot of work into making super resolution feasible on client devices, it is still something which needs to be managed. Here, we provide some initial performance benchmarks for the same demos shown above, in the demos sections.

Performance Considerations

AI Upscaling does require some computational effort, however it is mostly on the graphics card, so AI Upscaling's impact on CPU is limited. The amount of computation (and therefore the framerate / performance) depends on the size of input video you are upscaling

The following table should give a rough idea performance for different input video resolutions. These results are only for Web environments. Our mobile SDKs will have access to more powerful native libraries, enabling significantly better performance.

240p -> 480p/720p 360p -> 720p/1080p 480p -> 960p/1440p
High End Smartphone 120 fps 40 fps 14fps
Mid-range Smartphone 80 fps 28 fps 9 fps
Low-End Smartphone 20fps 6fps 3fps
Mid-range Laptop 100fps 35fps 8fps
GPU Desktop 200+fps 200+fps 80 fps

You can measure the fps on any given device by adding the analyticsEnabled flag as true in the configuration parameters.

  const config = {
	   token: '...',
	   analyticsEnabled: true

   const upscaler = vectorlyUpscaler(video, config);

You can then measure the fps at any time with upscaler.metrics.fps property. The fps number provided by upscaler.metrics.fps will not exceed the source video's frame rate because we only render when a video frame changes.

It's recommended to stick to 240p or 360p inputs, as mid-range devices tend to struggle with larger inputs. You can also disable upscaling if the fps gets too low.


The primary benefit of Super resolution is to increase video quality. Using the original high-resolution video as a reference, we can use traditional video quality metrics like VMAF to quantify the quality improvement of Super Resolution, when compared to normal bicubic upscaling of the downsampled / low-resolution video content.

Our general AI upscaler filter generally achieves a 10 to 15 point VMAF improvement compared to bicubic scaling. With content-specific AI models, or heavier models, we will likely be able to achieve further quality gains. We are currently working on releasing quality comparisons for content specific models.

Quality visualization

For reference, below are side by side comparisons of bicubic upscaling of the low-resolution original / Super resolution of the low-resolution / High resolution original


Bicubic (240p)
240p upscaled to 720p
Original 720p


Bicubic (240p)
240p upscaled to 720p
Original 720p


Bicubic (240p)
240p upscaled to 720p
Original 720p