Skip to content

Build

Private recording

Record camera broadcasts directly to Cloudflare R2 with prepaid recording credit, bounded uploads, private playback and 90-day retention.

Private recording

Recording has its own prepaid balance: 1 recording credit = ₹1 of service. Add credit in Recording balance, then choose the quality, fps and optional monthly spending limit in each project's Recording tab. Every top-up adds to the balance, including repeated purchases of the same package. RTC minutes, the generic credit wallet and SVGA balances are unchanged. Developers can integrate before recording is enabled; capture needs a configured service, an enabled project and sufficient recording credit.

Media uploads from the user's device directly to private Cloudflare R2. VoltKit's backend issues short-lived upload links, verifies payments and saves metadata. Video chunks, validation input and playback do not traverse VoltKit's VPS, API or RTC servers. Validation runs inside Cloudflare; authenticated playback goes from R2 through a Cloudflare Worker to the viewer.

Recording quality and eligible publishers

Only camera-on hosts and cohosts in a live room may start recording. Viewers, screen-only publishers and moderated/paused cameras cannot start a new capture. Each recorded camera creates its own files. Viewers do not multiply storage; playing a file repeatedly incurs additional playback usage.

Recording quality is independent of live RTC quality. New projects default to 480p / 15 fps, 600 kbps video + 48 kbps audio. Choose 360p / 15 fps for the lowest rate, or 360p/480p/720p/1080p at 15 or 30 fps. The super admin controls each preset's bitrate, availability and price. Owners choose an enabled preset in the console; SDK code cannot request arbitrary resolution, bitrate, fps or pricing. All projects use the account's shared recording balance.

Each file freezes its recording rate, playback rate and quality when it starts. Console changes affect new files, including the next automatic file rotation; a file already in progress keeps its original terms. Actual frames, bitrate, duration and fps are validated inside Cloudflare before playback. Invalid media is deleted and its reserved credit is released after confirmed cleanup.

Browser SDK

Requires @voltkit-rtc/client 0.6.0 or newer. Recording-credit billing uses the existing recording API; it does not require a new SDK release. SDK 0.5.x does not include recording APIs.

await client.startRecording({
  apiUrl: 'https://api.voltkit.in/api/v1',
  chunkMiB: 10, // optional: admin-allowed 5 or 10 MiB
  onState: ({ state, bufferedBytes, uploadedBytes, error }) => {
    // Show recording/backpressure/error independently of the live room.
  },
  onRecordingReady: (file) => {
    // Save file.id, file.playbackUrl and file.retainUntil in your database.
  },
});

// Flush recording before unpublishing the camera or closing the room.
const files = await client.stopRecording();

Camera frames are copied into a policy-sized canvas. Microphone audio is cloned when available and follows microphone mute state. Recording never changes the original live camera, microphone or RTC encoding. Front/back camera changes rotate to a new recording file. Demotion or camera-off stops capture.

The default upload chunk is 10 MiB (10,485,760 bytes), with optional 5 MiB presets allowed by the admin. These are upload-part sizes, not video duration. Browser capture rotates into independently playable files every 300 seconds by default. Small files use a final multipart tail smaller than the configured chunk. Two uploads run concurrently by default, within a 40 MiB SDK buffer ceiling. Slow uploads pause recording rather than accumulate unlimited RAM; RTC remains live, so paused recording periods are gaps and must be shown to the user. Persistent failure stops recording and cleans up its upload. Background completion/validation jobs are bounded. Browser recordings do not survive a page close; call stopRecording() and wait before leaving.

Flutter SDK

Requires voltkit_rtc 0.6.0, prepared but unpublished. Android camera capture is an explicit video-only preview. Android APK compilation and local Dart upload tests pass; native capture on physical devices has not been verified. iOS native recording is unavailable. Ordinary live camera/microphone and the independent screen-sharing APIs remain unchanged.

await client.startRecording(
  apiUrl: Uri.parse('https://api.voltkit.in/api/v1'),
  androidCapturePreviewEnabled: true,
  onRecordingReady: (file) {
    // Save ID, stable owner link and expiry.
  },
);
final files = await client.stopRecording();

Android uses the current camera track, encodes short MP4 files (up to 60 seconds) in the application's cache and keeps at most two files. Native upload streams parts from disk with bounded concurrency. When uploading falls behind, capture waits instead of filling storage. Temporary files are removed after upload or failure. Do not kill the application during flushing. The preview does not record native microphone audio. Applications with their own camera+microphone encoder may use RecordingUpload.create and uploadFile; use the returned policy and keep the finalized file immutable until upload finishes. Encoding must respect policy dimensions, bitrates, frame rate and segment duration.

Billing and balances

Rates are per 1,000 recorded camera-minutes, with 90-day storage included. One camera recorded for 30 minutes uses 30 minutes; three cameras recorded for 30 minutes use 90. Viewer count does not multiply recording minutes.

Initial rates (the console shows the current admin-configured offer):

Quality15 fps / 1,000 minutes30 fps / 1,000 minutes
360p₹69₹99
480p₹99₹139
720p₹199₹279
1080p₹349₹449

For example, 1,000 minutes of 360p / 15 fps uses 69 credits. Default top-ups: ₹500 → 500 credits; ₹2,000 → 2,060 credits (3% extra); ₹5,000 → 5,250 credits (5% extra). Custom top-ups start at ₹100 with 1 credit per rupee. Bonuses, packages and rates are admin-configurable. Rate discounts appear separately from extra-credit offers; neither changes a completed purchase.

The backend reserves the maximum file charge before capture, including its five-second encoding allowance. With the default 300-second file, 360p/15 fps holds ₹0.35075; 480p/15 fps holds ₹0.50325. Concurrent captures each need their own hold. Verified duration is rounded up to a millisecond, then charged to a millionth of a rupee; there is no per-file whole-minute or whole-paise rounding. Unused holds return automatically on completion, or after confirmed abort cleanup. Deleting/expiring a completed recording does not refund consumed credit.

Playback/downloads use the same recording balance, initially ₹2 per decimal GB (1,000,000,000 bytes). The file's rate is frozen at capture. R2 Internet egress is free; this is VoltKit's private delivery and metering service charge. Playback is additional to the recording-minute rate. Upload chunks remain binary MiB.

Project monthly limits include recording and playback charges plus active holds. A limit of ₹0 means no project-specific ceiling; the prepaid account balance still limits usage. Months follow UTC, and each file is assigned to its start month. A project's balance is shared with its other cameras; a second project cannot spend funds already held by the first.

Razorpay orders and the recording ledger are separate from other products. Verified capture adds credit exactly once even after duplicate callbacks/webhooks. If payment confirmation is interrupted, retry confirmation or refresh purchase history; do not pay again for that order. Admin free-credit grants add to the existing balance and are audited. Previously purchased GB quotas and old captured orders remain usable through the project's legacy quota option; they are not converted, deleted or consumed by recording-credit purchases.

Saved URLs and private playback

After upload and validation complete, metadata returns:

{
  "id": "YOUR_RECORDING_ID",
  "playbackUrl": "https://www.voltkit.in/dashboard/rtc/recordings/YOUR_RECORDING_ID",
  "retainUntil": "ISO_8601_EXPIRY",
  "state": "ready"
}

Save the recording ID, stable owner-console link and expiry. The console link requires the owning VoltKit account to sign in. It is not a permanently public R2 URL. The media URL is short-lived (10 minutes) and must be regenerated when needed. For playback inside your own app, your backend first authenticates and authorizes its user, then requests a playback link with its project's RTC secret:

POST /api/v1/recording/upload/server/playback
Content-Type: application/json
X-API-Key: YOUR_RTC_API_SECRET

{"recordingId":"YOUR_RECORDING_ID"}

Response data.url is the private Cloudflare media URL; data.expiresIn is 600. Return it only to authorized users and avoid storing/logging the ticket. The project key cannot access another project's recordings. Never expose RTC API secrets or R2 credentials in a browser/mobile app. Playback range requests reserve the charge for requested bytes and refund unsent bytes after trusted settlement. If settlement is lost, the reservation stays conservatively charged for support investigation; it is never automatically refunded while a stream may still run. A full download needs enough recording credit for the entire file (or funded legacy delivery quota for legacy files).

Ninety-day deletion

Expiry is 90 days after R2 upload completion, recorded in retainUntil. Playback access stops at that time, including previously issued media tickets. The backend cleanup job requests R2 deletion; a bucket lifecycle rule removes objects after 90 days as a fallback even during application downtime. Cloudflare lifecycle deletion is asynchronous, typically within 24 hours of expiration, so exact-second physical deletion is not promised. Save/download any recording you need longer before expiry. The stable link and database record do not keep the deleted media alive. Incomplete uploads are also cleaned up automatically.

Native device acceptance and real Cloudflare upload/validation/deployment remain pending. Deployment prerequisites are in the repository's voltkit-rtc/docs/RECORDING-R2-SETUP.md. No recording keys belong in a client SDK.

Last updated . Something unclear or wrong? Tell us.