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.
| Quality | Landscape / portrait | Top at 30 fps | Top at 60 fps | Viewer data/hour at 30 / 60 fps |
|---|---|---|---|---|
| 1080p | 1920×1080 / 1080×1920 | 6 Mbps | 9 Mbps | about 2.75 / 4.10 GB |
| 720p | 1280×720 / 720×1280 | 3 Mbps | 4.5 Mbps | about 1.40 / 2.07 GB |
| 480p | 854×480 / 480×854 | 1.2 Mbps | 1.8 Mbps | about 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().frameRateis what the browser opened the camera at;getVideoStats()reports the rate actually captured assource.fps(where the browser supplies it) and the rate sent asfps. When the camera captures under 50 fps, the SDK sends 30 fps and warnsframe-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 send | Landscape camera request | Portrait camera result |
|---|---|---|
| 1080p | 1920×1080 | 1080×1920 |
| 720p | 1280×720 | 720×1280 |
| 480p | 854×480 | 480×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.
| Quality | Desktop layers at 30 fps (small → full) | Phone/tablet layers |
|---|---|---|
| 1080p | 480×270 at 0.4 Mbps · 960×540 at 2 Mbps · 1920×1080 at 6 Mbps | Half and full size |
| 720p | 320×180 at 0.2 Mbps · 640×360 at 0.8 Mbps · 1280×720 at 3 Mbps | Half and full size |
| 480p | 427×240 at 0.2 Mbps · 854×480 at 1.2 Mbps | Same 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.
| Mode | Frame rate | Top / lower bitrate | Publisher upload before audio |
|---|---|---|---|
| Detail: slides and text | 15 fps | 2.5 / 0.6 Mbps | 3.1 Mbps |
| Motion: video and games | 30 fps | 6 / 0.6 Mbps | 6.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 profile | Desktop / phone upload before audio | Each full-size viewer, including 0.1 Mbps sound allowance |
|---|---|---|
| 1080p30 | 8.4 / 8 Mbps | 6.1 Mbps |
| 1080p60 | 11.4 / 11 Mbps | 9.1 Mbps |
| 720p30 | 4 / 3.8 Mbps | 3.1 Mbps |
| 720p60 | 5.5 / 5.3 Mbps | 4.6 Mbps |
| 480p30 | 1.4 Mbps | 1.3 Mbps |
| 480p60 | 2 Mbps | 1.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.
| Profile | Sent as | Top layer | Billed as |
|---|---|---|---|
| Standard audio | mono Opus | 64 kbps | standard audio |
| HD audio | stereo Opus | 128 kbps | HD audio |
| 480p at 30 fps | 2 layers | 1.2 Mbps | video |
| 720p at 30 fps | 3 layers | 3 Mbps | video |
| 1080p at 30 fps | 3 layers | 6 Mbps | video |
| 480p at 60 fps | 2 layers, top at 60 fps | 1.8 Mbps | 480p at 60 fps |
| 720p at 60 fps | 3 layers, top at 60 fps | 4.5 Mbps | 720p at 60 fps |
| 1080p at 60 fps | 3 layers, top at 60 fps | 9 Mbps | 1080p 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 (
mediaPermissionsin 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-allowedoraudio-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.
Related
- JavaScript SDK and Flutter SDK.
- Screen sharing and Troubleshooting.
- Usage and billing and Rooms.
Last updated . Something unclear or wrong? Tell us.