Skip to content

Build

Video Quality: 1080p, 720p and 480p

Every quality on every project, billed by the media used: 30/60 fps, adaptive layers, viewer quality choices, screen sharing and device troubleshooting.

1080p, 720p and 480p are available on every project, during and after the trial. Up to 60 fps. There is no HD upgrade or request: choose a quality for the device and connection. Each participant is billed by the media used, at rates per 1,000 participant-minutes: video up to 1080p at 30 fps costs the same whatever its size, HD audio included, and 60 fps costs more only while it is delivered. See With the VoltKit Wallet. Free and previously bought minutes are used first, one per participant-minute at any quality.

The browser and Flutter 0.5.0 profiles below default to 30 fps. Opt into 60 fps for smoother motion; it uses more data, battery and processing. A camera or device that cannot sustain the requested rate falls back to 30 fps and reports a warning; most phone and laptop cameras give a browser 30 fps (see 60 fps needs a 60 fps capture source). These are encoding ceilings, not a promise that every camera or connection delivers them.

QualityLandscape / portraitTop at 30 fpsTop at 60 fpsViewer data/hour at 30 / 60 fps
1080p1920×1080 / 1080×19206 Mbps9 Mbpsabout 2.75 / 4.10 GB
720p1280×720 / 720×12803 Mbps4.5 Mbpsabout 1.40 / 2.07 GB
480p854×480 / 480×8541.2 Mbps1.8 Mbpsabout 0.59 / 0.86 GB

Data estimates add 0.1 Mbps for sound and rounding, use decimal GB, and assume the viewer receives one full-size stream throughout. Adaptive video, silence, packet overhead, music and additional streams change actual use. Minutes count everyone connected, including viewers; see Usage and billing.

60 fps needs a 60 fps capture source

60 fps is delivered only when the capture source provides it. Phone makers usually keep 60 fps for their own camera app, so browsers and most apps get 30 fps. Check a camera's supported rates in chrome://media-internals → Video Capture. Billing follows the frame rate actually delivered.

  • What we measured. A laptop webcam gave Chrome 20 fps at 1080p. A phone whose own camera app records 1080p at 60 fps gave Chrome and Opera 25–30 fps from both cameras in good light, and 14 fps in a dim room.
  • Check the rate you got, not the one you asked for. track.getSettings().frameRate is what the browser opened the camera at; getVideoStats() reports the rate actually captured as source.fps (where the browser supplies it) and the rate sent as fps. When the camera captures under 50 fps, the SDK sends 30 fps and warns frame-rate-fallback.
  • For 60 fps, use a webcam or capture card that the browser lists at 60 fps, or a virtual camera such as OBS Virtual Camera with OBS set to 60 fps. More light also helps: cameras slow down in dim rooms (see Troubleshooting).

Check the project policy

Every project defaults to maxQuality: "1080p" and maxFrameRate: 60. A super admin may apply an explicit operator restriction to a project; this is independent of the plan, payment and trial.

await client.connect();
console.log(client.videoPolicy.maxQuality);   // normally "1080p"
console.log(client.videoPolicy.maxFrameRate); // normally 60

Policy changes take effect when a client next joins or reconnects. Existing sessions retain their joined policy. If a project is explicitly restricted, the SDK clamps a higher publish request to its allowed profile.

Choose a quality

JavaScript (browser), SDK 0.5.0

Capture the size and frame rate you want, then publish. The SDK scales down, never up: a 720p camera cannot produce 1080p.

const media = await navigator.mediaDevices.getUserMedia({
  audio: true,
  video: {
    width: { ideal: 1920 }, height: { ideal: 1080 },
    frameRate: { ideal: 30, max: 30 },
  },
});
await client.publish(media, undefined, { quality: "1080p", frameRate: 30 });

// Opt in while live. Returns 30 if capture/device/policy requires fallback.
const actualFps = await client.setVideoFrameRate(60);

// For a lower quality, resize the camera first, then change the encoding.
await media.getVideoTracks()[0].applyConstraints({
  width: { ideal: 854 }, height: { ideal: 480 },
});
const qualityUsed = await client.setVideoQuality("480p");

publish() accepts layers, codec and degradationPreference too. Phones normally send two layers, desktop three where the captured picture allows. Prefer "maintain-framerate" on weak phones; forcing "maintain-resolution" on software encoders can reduce the stream to a few frames per second. The demo follows that rule.

await client.publish(media, undefined, {
  quality: "720p", frameRate: 30, layers: 2,
  codec: "auto", degradationPreference: "maintain-framerate",
});

Flutter, SDK 0.5.0

await client.publish(video: const VideoPublishOptions(
  quality: VideoQuality.p1080,
  frameRate: 30, // default; opt into 60
));

maxFramerate remains a compatible alias. Native capture may deliver less than requested; watch onPublishWarning for frame-rate-fallback. To change the camera profile, unpublish and publish with new options. Unpublishing ends the camera, screen and music; use unpublishScreen() to stop only the screen. See the Flutter API.

Android and iOS preview SDKs

The preview native SDKs retain their documented capture and encoding defaults. They have not gained the browser/Flutter 0.5.0 preset, frame-rate or screen-share helpers. Configure their native capture and encoding explicitly rather than assuming a new bitrate profile from the server policy alone.

Capture at least that size

To sendLandscape camera requestPortrait camera result
1080p1920×10801080×1920
720p1280×720720×1280
480p854×480480×854

video: true often returns 640×480. Check track.getSettings() for the actual size and frame rate. An upright phone camera may report its sensor in landscape terms; preserve its real orientation and avoid stretching. Portrait video uses the same quality tier and bitrate.

What is sent

Simulcast sends several copies. Each viewer receives the largest copy their connection can carry, moving down and up as conditions change.

QualityDesktop layers at 30 fps (small → full)Phone/tablet layers
1080p480×270 at 0.4 Mbps · 960×540 at 2 Mbps · 1920×1080 at 6 MbpsHalf and full size
720p320×180 at 0.2 Mbps · 640×360 at 0.8 Mbps · 1280×720 at 3 MbpsHalf and full size
480p427×240 at 0.2 Mbps · 854×480 at 1.2 MbpsSame two

At 60 fps only the full-size layer rises to 60 fps and 1.5× bitrate. Every lower layer stays at 30 fps. Layers follow actual captured dimensions; a smaller camera may use fewer layers. Two-layer phones retain the full-size layer as the device adapts.

The encoder starts at a 2 Mbps estimate and the viewer transport at 4 Mbps. A 1080p viewer can start below the top layer while congestion control measures the path. Ramp time depends on the device, content and network; the stats show what actually arrives.

Viewer quality menu

SDK 0.5.0 provides a helper above setPreferredLayers:

// Use the producerId from a remote video `track` event.
await client.setReceiveQuality(producerId, "480p");
await client.setReceiveQuality(producerId, "auto");

Offer Auto (recommended) / 1080p / 720p / 480p. Explicit choices map to that producer's real videoLayers, including two-layer phones, portrait video and screens: the largest copy within the requested size, or the smallest available when none fits. It cannot create a missing resolution. A slower connection may receive less. Reconnect restores the preference for that source; an older publisher without metadata rejects explicit presets with quality-unavailable, while Auto remains usable.

For small tiles, setPreferredLayers remains available. Flutter uses the consumer/producer identifiers documented in its API; see Flutter SDK.

Screen sharing

Camera and screen can be live together for a host or co-host. Screen sharing covers capture, tab/system audio, permissions, stopping and platform setup.

ModeFrame rateTop / lower bitratePublisher upload before audio
Detail: slides and text15 fps2.5 / 0.6 Mbps3.1 Mbps
Motion: video and games30 fps6 / 0.6 Mbps6.6 Mbps

Screens are capped to native 1080p and never upscaled. A small screen may have one layer. Screen audio is a separate screen-audio track; availability depends on the browser/platform. Detail mode preserves legibility with less data, while motion mode spends more on movement.

Codec

Codec licensing. VP8 and Opus are royalty-free. H.264 is patented and licensed through the Via LA patent pool. The H.264 encoders in browsers and phones are licensed by their makers for their own users, and VoltKit's servers only forward video, never encode it. If you sell video or stream commercially at scale, check Via LA's current terms with your lawyer. If you prefer to stay royalty-free, publish with codec: "vp8"; phones then encode in software, which costs more battery and processing.

From version 0.4.0 the browser SDK picks the video codec for each device (codec: "auto", the default):

  • Phones and tablets send H.264 when the device encodes it in hardware. The SDK asks the browser through navigator.mediaCapabilities; where the browser cannot say, iPhones and iPads count as having it (they all do) and other devices do not. A hardware encoder does the work outside the phone's processor, which is what keeps the picture sharp while the phone also runs your app.
  • Computers send VP8, which they encode well in software, and which every browser that can watch decodes.

codec: "vp8" or codec: "h264" forces one. When the browser cannot send a forced H.264, the SDK publishes VP8 instead and reports it on the error event; publishing never fails because of the codec. A viewer whose browser cannot decode H.264 cannot watch an H.264 stream, so force codec: "vp8" if your audience uses such browsers (see a viewer's browser cannot play H.264).

Flutter prefers H.264 when the device offers it (preferHardwareCodec, on by default); codecMimeType forces one and reports on onPublishWarning when the device cannot.

Check what is being sent

const sent = await client.getVideoStats(); // null without a camera
// width, height, fps, bitrate, qualityLimitation and every outgoing layer
const received = await client.getReceiveStats(producerId);

Sample every second or two; the first bitrate reading is zero. qualityLimitation: "cpu" means the device cannot encode fast enough, "bandwidth" means upload is limiting it, and "none" means neither reported a limit. Some browsers omit that field. The videoLimited event reports a sustained change; a fallback warning reports when 60 fps cannot be used. See Troubleshooting.

Limits the service enforces

A shared 25 Mbps incoming safety cap per publisher covers camera, screen, microphone and music together. It is the same for every included quality and plan. It is separate from the frame/quality operator restrictions. The free trial's participant limit does not change video quality.

Data and bandwidth

Camera profileDesktop / phone upload before audioEach full-size viewer, including 0.1 Mbps sound allowance
1080p308.4 / 8 Mbps6.1 Mbps
1080p6011.4 / 11 Mbps9.1 Mbps
720p304 / 3.8 Mbps3.1 Mbps
720p605.5 / 5.3 Mbps4.6 Mbps
480p301.4 Mbps1.3 Mbps
480p602 Mbps1.9 Mbps

The host uploads every layer; a viewer downloads one. Add screen upload separately when sharing alongside the camera, and budget for each remote camera/screen a viewer receives. Voice commonly adds around 64 kbps while speaking, music up to 128 kbps stereo, plus transport overhead. Keep spare upload capacity; these ceilings alone are not a connection recommendation. Auto or a manual lower viewing quality reduces data use.

Troubleshooting

  • Asked for 1080p but received 720p: inspect the project's joined policy, camera size, actual outgoing layers and the viewer's download. If policy is restricted, contact support; changing plans does not remove an operator restriction.
  • 60 fps falls back or heats a phone: keep 30 fps, turn off effects and try 720p or 480p. Monitor real stats instead of relying on the requested constraints.
  • Software-encoded phone is sharp but barely moving: remove maintain-resolution; preserve frame rate and lower the quality. Send two layers and the camera track directly when effects are off.
  • Screen is blank or sharing does not start: desktop capture needs a supported secure browser and a user click. Choose a screen/window/tab; check platform setup and screen-share-unsupported. Most mobile browsers cannot capture a screen.
  • 480p looks like 240p: update old SDKs and ensure the producer describes its real layers. Do not request more layers than the camera can send.

With the VoltKit Wallet

Every account pays from the VoltKit Wallet. Prices are in credits per 1,000 participant-minutes: standard audio 39, HD audio 49, video up to 1080p at 30 fps 99 (HD audio included), and 60 fps 109, 124 or 149 at 480p, 720p or 1080p. One participant connected for one minute = one participant-minute. The current rates are on Usage and billing and Wallet > Rates. Free and previously bought minutes are used before any credit, one per participant-minute whatever the quality.

Media profiles

Each mode is a media profile: its codec, its bitrates and the rate it is billed at. The media server caps every stream at its profile's maximum, so a client cannot send more than its mode allows. These are the launch profiles; the bitrates match the SDK ladders above.

ProfileSent asTop layerBilled as
Standard audiomono Opus64 kbpsstandard audio
HD audiostereo Opus128 kbpsHD audio
480p at 30 fps2 layers1.2 Mbpsvideo
720p at 30 fps3 layers3 Mbpsvideo
1080p at 30 fps3 layers6 Mbpsvideo
480p at 60 fps2 layers, top at 60 fps1.8 Mbps480p at 60 fps
720p at 60 fps3 layers, top at 60 fps4.5 Mbps720p at 60 fps
1080p at 60 fps3 layers, top at 60 fps9 Mbps1080p at 60 fps

Every video mode includes HD audio in its rate.

60 fps is billed only while delivered

The media server measures the frame rate a stream really delivers. A stream counts as 60 fps after two samples at 50 fps or more, about half a minute after it starts, and stops counting as soon as a sample falls below 40 fps. Its 60 fps rate follows the size of the layers running at 60 fps: 480p, 720p or 1080p. A 60 fps request that the camera, device or connection cannot keep up is billed at 30 fps, and a viewer who receives only the 30 fps lower layers is billed at 30 fps.

Project media settings

On the project's Settings tab you choose what the project may use: audio on or off, video on or off, the largest video quality (480p, 720p or 1080p), the frame rate (30 or 60) and the audio quality (standard or HD).

  • Allowing a mode allows every mode below it. 1080p at 60 fps opens 720p and 480p at 60 fps and every 30 fps mode.
  • Allowing is free. Each participant is billed for what they actually use, never for what the project allows.
  • The SDKs read the settings when they join (mediaPermissions in the browser and Flutter SDKs from 0.7.0) and never send what the project refuses; the server enforces them whatever a client sends. A refused or capped request carries a code: media-disabled, frame-rate-not-allowed, video-quality-not-allowed or audio-quality-not-allowed (see Errors).

How many videos one participant receives

To keep the video rate fair for everyone, a participant receives at most 25 videos at once, and at most 4 of them above 360p; the others are held at 360p. A video sent as a single layer cannot be held at 360p and only counts toward the 25. The SDKs wait for a free place instead of failing, with one video-subscription-limit warning per video waiting.

HD audio is measured

A microphone is billed as HD audio only when it asks for HD (SDK audioQuality: 'hd', declared above 64 kbps) or is measured above 72 kbps. A stereo flag alone is not HD: browsers negotiate stereo on every microphone, and speech stays well below that. The SDKs' standard audio is held at 60 kbps, so it is never measured as HD. A project limited to standard audio is never billed HD audio: the SDKs send standard audio there, and the server refuses a microphone that declares HD.

In-app music (publishMusic) is a 128 kbps stereo track, and it is free: broadcast music in HD (128 kbps stereo) during live, host and talk sessions — included free; it never changes your rate. It never raises anyone to HD audio, and a project limited to standard audio publishes it too.

Last updated . Something unclear or wrong? Tell us.