@vidtreo/recorder 1.7.13 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -10
- package/dist/index.d.ts +3460 -2913
- package/dist/index.js +5964 -2265
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -8,6 +8,32 @@ Vidtreo SDK for browser-based video recording and transcoding. Similar to Ziggeo
|
|
|
8
8
|
npm install @vidtreo/recorder
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Direct Upload Transport
|
|
12
|
+
|
|
13
|
+
Starting with `@vidtreo/recorder` 1.8.0, video bytes upload directly from the browser to Vidtreo-managed R2 storage through a resumable multipart session. The part size scales with the file (`calculateDirectUploadPartSize`: about 64 parts, whole MiB, clamped to 5–64 MiB) and the backend returns the authoritative geometry plus the parts already verified by R2. The SDK uploads up to four missing parts at once, computes a SHA-256 manifest checksum plus an R2-supported MD5 transport checksum, obtains a short-lived URL whose Signature V4 headers bind `Content-MD5` to that part, and confirms only after all parts succeed.
|
|
14
|
+
|
|
15
|
+
- The API key is sent only to the configured Vidtreo backend and is never sent to R2.
|
|
16
|
+
- Upload progress reaches `100%` only after the backend accepts confirmation.
|
|
17
|
+
- `onProgress` is a phased, eased value: preparation and initiation fill 0–5%, transferred bytes fill 5–90%, confirmation eases from 90% toward 99%. It never moves backwards. `onProgressDetails` keeps the raw `loadedBytes`/`totalBytes` and reports the current `phase` (`UploadProgressPhase`: `"preparing" | "transferring" | "finalizing" | "completed"`).
|
|
18
|
+
- Each part is retried up to five times with exponential backoff on network errors, inactivity, and HTTP 408/429/5xx. Retries pause while the browser is offline, and an expired part URL is re-signed once.
|
|
19
|
+
- Pending uploads keep one idempotency identity across offline recovery and retries.
|
|
20
|
+
- Retries resume provider-verified parts and never store multipart URLs or checksums in IndexedDB.
|
|
21
|
+
- Aggregate progress remains monotonic across parts and reaches `100%` only after confirmation.
|
|
22
|
+
- If confirmation is retried after the backend already accepted the upload, the SDK returns the existing video without uploading the Blob again.
|
|
23
|
+
- Signed upload URLs remain in memory for one attempt and are not stored in IndexedDB or included in SDK errors.
|
|
24
|
+
|
|
25
|
+
### Persistent Upload Queue and Resume
|
|
26
|
+
|
|
27
|
+
Recordings and native-camera uploads are written to an IndexedDB upload queue before they are sent. If the page is reloaded, the tab crashes, or the network drops, the next recorder instance on the same origin picks the upload up again:
|
|
28
|
+
|
|
29
|
+
- The queued upload keeps its idempotency key, so the backend returns the same video and the SDK uploads only the parts R2 has not verified yet.
|
|
30
|
+
- Failed attempts are retried by the queue with exponential backoff (up to 10 attempts) on top of the per-part retries. Retries wait while the browser is offline.
|
|
31
|
+
- Uploads authenticated with `apiKey` store the key with the queued item. Uploads authenticated with `getAccessToken` never persist a token: the provider is called again when the upload resumes, so the page must create the recorder (or native-camera handler) with the same `getAccessToken` after a reload.
|
|
32
|
+
- Errors that cannot succeed on retry (such as `upload.insufficient-credits`) are reported immediately and are not retried.
|
|
33
|
+
- When IndexedDB is unavailable (private browsing, blocked storage) the SDK uploads directly without persistence.
|
|
34
|
+
|
|
35
|
+
Version 1.8.0 does not fall back to the legacy Worker-proxied upload endpoint. Applications that need the previous transport must remain on an earlier SDK version while the backend continues to support those clients. Custom Vidtreo backend deployments must expose initiation, part authorization, and confirmation endpoints. Managed R2 CORS must allow browser `PUT` plus the signed `Content-MD5` header and expose `ETag`.
|
|
36
|
+
|
|
11
37
|
## Quick Start
|
|
12
38
|
|
|
13
39
|
```typescript
|
|
@@ -46,7 +72,8 @@ Creates a new recorder instance with the specified configuration.
|
|
|
46
72
|
|
|
47
73
|
**Parameters:**
|
|
48
74
|
|
|
49
|
-
- `config.apiKey` (required): Your API key for backend authentication
|
|
75
|
+
- `config.apiKey` (required unless `getAccessToken` is set): Your API key for backend authentication
|
|
76
|
+
- `config.getAccessToken` (optional): `() => Promise<string>` that returns a short-lived desktop access token. Use it instead of `apiKey` when your server mints temporary tokens, so no long-lived key is exposed in the page. The SDK calls it whenever it needs a fresh token, including when a queued upload resumes after a reload. Provide exactly one of `apiKey` or `getAccessToken`
|
|
50
77
|
- `config.apiUrl` (optional): Your backend API URL endpoint. Defaults to `https://core.vidtreo.com` if not provided
|
|
51
78
|
- `config.enableSourceSwitching` (optional): Enable switching between camera and screen during recording. Default: `false`
|
|
52
79
|
- `config.enableMute` (optional): Enable mute/unmute functionality. When disabled, `muteAudio()`, `unmuteAudio()`, and `toggleMute()` will throw errors. Default: `true`
|
|
@@ -55,15 +82,15 @@ Creates a new recorder instance with the specified configuration.
|
|
|
55
82
|
- `config.maxRecordingTime` (optional): Maximum recording duration in milliseconds. When the maximum time is reached, recording automatically stops. If not set, recording can continue indefinitely until manually stopped. Examples: `300000` (5 minutes), `600000` (10 minutes), `1800000` (30 minutes)
|
|
56
83
|
- `config.countdownDuration` (optional): Countdown duration in milliseconds before recording starts. Default: `0` (no countdown)
|
|
57
84
|
- `config.userMetadata` (optional): Custom metadata object to include with uploads
|
|
58
|
-
- `config.onUploadComplete` (optional): Callback invoked when upload completes successfully
|
|
85
|
+
- `config.onUploadComplete` (optional): Callback invoked when upload completes successfully. Receives a [`RecordingUploadResult`](#recordinguploadresult)
|
|
59
86
|
- `config.onUploadProgress` (optional): Callback invoked during upload with progress value (0-1)
|
|
60
|
-
- `config.onUploadError` (optional): Callback invoked when upload fails
|
|
87
|
+
- `config.onUploadError` (optional): Callback invoked when upload fails. Read the error code with [`getUploadErrorCode`](#upload-errors)
|
|
61
88
|
- `config.onAudioWarning` (optional): Callback invoked when an audio issue is detected during recording. Receives an `AudioWarning` object. See [Audio Warnings](#audio-warnings) for details
|
|
62
89
|
- `config.onRecordingStart` (optional): Callback invoked when recording starts
|
|
63
90
|
- `config.onRecordingStop` (optional): Callback invoked when recording stops
|
|
64
91
|
- `config.onError` (optional): Callback invoked when a stream error occurs
|
|
65
92
|
|
|
66
|
-
**Throws:** `Error` if `apiKey` is
|
|
93
|
+
**Throws:** `Error` if neither `apiKey` nor `getAccessToken` is provided. If both are provided it throws the shared configuration error `"Provide either apiKey or getAccessToken, not both"` with `code: CONFIG_ERROR_CREDENTIAL_CONFLICT` (`"config.credential-conflict"`). The same error is thrown by `RecorderController.initialize`, `ConfigManager`, `ConfigService`, `NativeCameraHandler` and `VideoUploadService`, and by the React, Vue and Web Component packages. Use `isCredentialConflictError(error)`, `hasCredentialConflict(apiKey, getAccessToken)` or `assertSingleCredential(apiKey, getAccessToken)` to check it yourself.
|
|
67
94
|
|
|
68
95
|
#### Methods
|
|
69
96
|
|
|
@@ -123,6 +150,14 @@ Switches the recording source between camera and screen during an active recordi
|
|
|
123
150
|
|
|
124
151
|
**Throws:** `Error` if source switching is not enabled or if not currently recording
|
|
125
152
|
|
|
153
|
+
##### `reselectScreenSource(): Promise<boolean>`
|
|
154
|
+
|
|
155
|
+
Opens the screen picker again while the source is `'screen'` or `'camera-screen'` and swaps the newly selected screen or window into the running preview or recording. The source type, the microphone and the camera bubble are kept.
|
|
156
|
+
|
|
157
|
+
**Returns:** `Promise<boolean>` - `true` when a new screen was applied, `false` when the picker was cancelled
|
|
158
|
+
|
|
159
|
+
**Throws:** `Error` if source switching is not enabled or no screen capture is active
|
|
160
|
+
|
|
126
161
|
##### `getAvailableDevices(): Promise<AvailableDevices>`
|
|
127
162
|
|
|
128
163
|
Retrieves the list of available camera and microphone devices.
|
|
@@ -200,7 +235,8 @@ Cleans up all resources, stops active streams, and cancels any pending operation
|
|
|
200
235
|
|
|
201
236
|
```typescript
|
|
202
237
|
interface VidtreoRecorderConfig {
|
|
203
|
-
apiKey
|
|
238
|
+
apiKey?: string;
|
|
239
|
+
getAccessToken?: () => Promise<string>;
|
|
204
240
|
apiUrl?: string;
|
|
205
241
|
enableSourceSwitching?: boolean;
|
|
206
242
|
enableMute?: boolean;
|
|
@@ -211,10 +247,8 @@ interface VidtreoRecorderConfig {
|
|
|
211
247
|
userMetadata?: Record<string, unknown>;
|
|
212
248
|
enableTabVisibilityOverlay?: boolean;
|
|
213
249
|
tabVisibilityOverlayText?: string;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
uploadUrl: string;
|
|
217
|
-
}) => void;
|
|
250
|
+
captureSystemAudio?: boolean;
|
|
251
|
+
onUploadComplete?: (result: RecordingUploadResult) => void;
|
|
218
252
|
onUploadProgress?: (progress: number) => void;
|
|
219
253
|
onUploadError?: (error: Error) => void;
|
|
220
254
|
onRecordingStart?: () => void;
|
|
@@ -238,6 +272,32 @@ interface RecordingStartOptions {
|
|
|
238
272
|
}
|
|
239
273
|
```
|
|
240
274
|
|
|
275
|
+
### RecordingUploadResult
|
|
276
|
+
|
|
277
|
+
Payload passed to every upload-complete callback (`VidtreoRecorder` `onUploadComplete`, `UploadCallbacks.onSuccess`, the upload queue, native-camera uploads, and the React/Vue/Web Component packages). It contains every field of the backend `VideoUploadResult` plus the backward-compatible `recordingId` and `uploadUrl` fields.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
type RecordingUploadResult = {
|
|
281
|
+
recordingId: string; // same value as id
|
|
282
|
+
uploadUrl: string; // "" when the backend has no URL yet
|
|
283
|
+
id: string;
|
|
284
|
+
publicId: string;
|
|
285
|
+
filename: string;
|
|
286
|
+
fileSize: number;
|
|
287
|
+
mimeType: string;
|
|
288
|
+
duration: number | null;
|
|
289
|
+
status: string;
|
|
290
|
+
createdAt: string;
|
|
291
|
+
transferStatus: "pending" | "transferring" | "transferred" | "failed" | null;
|
|
292
|
+
transferError: string | null;
|
|
293
|
+
transferredAt: string | null;
|
|
294
|
+
r2DeleteAfter: string | null;
|
|
295
|
+
r2DeletedAt: string | null;
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Use `createRecordingUploadResult(videoUploadResult)` to build this payload from a raw `VideoUploadService` result.
|
|
300
|
+
|
|
241
301
|
### RecordingStopResult
|
|
242
302
|
|
|
243
303
|
```typescript
|
|
@@ -251,9 +311,11 @@ interface RecordingStopResult {
|
|
|
251
311
|
### SourceType
|
|
252
312
|
|
|
253
313
|
```typescript
|
|
254
|
-
type SourceType = 'camera' | 'screen';
|
|
314
|
+
type SourceType = 'camera' | 'screen' | 'camera-screen';
|
|
255
315
|
```
|
|
256
316
|
|
|
317
|
+
`'camera-screen'` is opt-in and only used when passed explicitly. See [Screen-only capture and camera + screen](#screen-only-capture-and-camera--screen-advanced).
|
|
318
|
+
|
|
257
319
|
### RecordingState
|
|
258
320
|
|
|
259
321
|
```typescript
|
|
@@ -661,6 +723,34 @@ try {
|
|
|
661
723
|
}
|
|
662
724
|
```
|
|
663
725
|
|
|
726
|
+
### Upload Errors
|
|
727
|
+
|
|
728
|
+
Upload failures that need user-facing handling carry a stable code. Read it with `getUploadErrorCode`:
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
import {
|
|
732
|
+
getUploadErrorCode,
|
|
733
|
+
UPLOAD_ERROR_INSUFFICIENT_CREDITS,
|
|
734
|
+
type UploadErrorCode,
|
|
735
|
+
} from '@vidtreo/recorder';
|
|
736
|
+
|
|
737
|
+
const recorder = new VidtreoRecorder({
|
|
738
|
+
apiKey: 'your-api-key',
|
|
739
|
+
onUploadError: (error) => {
|
|
740
|
+
const code: UploadErrorCode | null = getUploadErrorCode(error);
|
|
741
|
+
if (code === UPLOAD_ERROR_INSUFFICIENT_CREDITS) {
|
|
742
|
+
showMessage('This video could not be uploaded. Please contact the site administrator.');
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
});
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
| Code | Meaning |
|
|
749
|
+
|------|---------|
|
|
750
|
+
| `upload.insufficient-credits` | The backend rejected the upload with HTTP 402 because the account has no credits left. The upload is not retried. |
|
|
751
|
+
|
|
752
|
+
`getUploadErrorCode` returns `null` for every other error (network failures, timeouts, and so on). Those errors are retried automatically before they are reported.
|
|
753
|
+
|
|
664
754
|
### Recording with Disabled Features
|
|
665
755
|
|
|
666
756
|
```typescript
|
|
@@ -701,6 +791,91 @@ For web component installation, usage, and examples, please refer to the [@vidtr
|
|
|
701
791
|
|
|
702
792
|
## Advanced Usage
|
|
703
793
|
|
|
794
|
+
### Screen-only capture and camera + screen (advanced)
|
|
795
|
+
|
|
796
|
+
These capabilities are opt-in. Without the options below the recorder behaves exactly as before: screen capture starts from the camera preview and `'camera-screen'` is never used.
|
|
797
|
+
|
|
798
|
+
#### Screen-only capture
|
|
799
|
+
|
|
800
|
+
Set `startScreenWithoutCamera: true` to start `startPreview('screen')` / `startRecording({}, 'screen')` with `getDisplayMedia` for video and a microphone-only `getUserMedia` request. The camera is never opened. Mute, audio levels, microphone changes and stopping work as with the camera. Switching to `'camera'` or `'camera-screen'` opens the camera at that moment. If the user cancels the screen picker, the start rejects with an error whose `code` is `ERROR_SCREEN_SELECTION_CANCELLED` (`'screen.selection-cancelled'`).
|
|
801
|
+
|
|
802
|
+
#### Camera + screen (`'camera-screen'`)
|
|
803
|
+
|
|
804
|
+
`'camera-screen'` records the screen as the full frame with the camera as a picture-in-picture bubble. Preview and recording use the same composed video track; audio is the microphone.
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
import { isPictureInPictureSupported, VidtreoRecorder } from '@vidtreo/recorder';
|
|
808
|
+
|
|
809
|
+
const recorder = new VidtreoRecorder({
|
|
810
|
+
apiKey: 'your-api-key',
|
|
811
|
+
enableSourceSwitching: true,
|
|
812
|
+
startScreenWithoutCamera: true,
|
|
813
|
+
pictureInPicture: { position: 'bottom-right', shape: 'circle', size: 0.22 },
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
if (isPictureInPictureSupported()) {
|
|
817
|
+
await recorder.startRecording({}, 'camera-screen');
|
|
818
|
+
await recorder.switchSource('screen');
|
|
819
|
+
await recorder.switchSource('camera-screen');
|
|
820
|
+
const cameraOnly = recorder.getCameraStream();
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
`pictureInPicture` options:
|
|
825
|
+
|
|
826
|
+
| Option | Type | Default | Description |
|
|
827
|
+
|--------|------|---------|-------------|
|
|
828
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' \| { x: number; y: number }` | `'bottom-right'` | Bubble corner, or the normalized top-left of the bubble in the output frame (0 to 1 each) |
|
|
829
|
+
| `shape` | `'circle' \| 'rounded'` | `'circle'` | Bubble shape |
|
|
830
|
+
| `size` | `number` (0.05 to 0.5) | `0.22` | Bubble diameter as a fraction of the frame height |
|
|
831
|
+
|
|
832
|
+
Invalid values throw during initialization. The bubble has a 3% margin, a white border of about 2% of its diameter and a soft shadow. The camera image is center-cropped into the bubble and is not mirrored.
|
|
833
|
+
|
|
834
|
+
- Switching between `'camera'`, `'screen'` and `'camera-screen'` works in preview and while recording, without stopping the recording.
|
|
835
|
+
- If the user stops sharing from the browser or OS, the recorder switches to the camera and keeps recording.
|
|
836
|
+
- Stopping, `stopPreview` and `cleanup()` stop the screen, camera and microphone tracks and the compositor.
|
|
837
|
+
- `getCameraStream()` returns a video-only stream with the live camera track (in `'camera'` and `'camera-screen'`), or `null`.
|
|
838
|
+
- Composition runs in a dedicated Web Worker (`MediaStreamTrackProcessor` → `OffscreenCanvas` → `MediaStreamTrackGenerator`). New frames drive it, not timers or `requestAnimationFrame`, so it keeps running while the page is hidden or minimized. Output resolution is the screen resolution capped to the recording resolution. Frame rate follows the screen capture, capped at 30 fps.
|
|
839
|
+
- `'camera-screen'` requires `MediaStreamTrackProcessor`, `MediaStreamTrackGenerator` and `OffscreenCanvas` (Chromium-based browsers and Electron). Check with `isPictureInPictureSupported()`. On other browsers the switch reports an error with code `ERROR_PICTURE_IN_PICTURE_UNSUPPORTED` (`'screen.picture-in-picture-unsupported'`) and the current source stays active.
|
|
840
|
+
|
|
841
|
+
#### Free position and live layout updates
|
|
842
|
+
|
|
843
|
+
A free `position` places the bubble's top-left corner at `{ x, y }` as fractions of the output frame width and height. The point is clamped so the whole bubble stays inside the frame for its size and the frame aspect ratio; corner presets keep their 3% margin, free positions have none.
|
|
844
|
+
|
|
845
|
+
`setPictureInPicture(update)` merges a partial update into the current configuration, validates it and applies it to the running compositor without restarting it, in preview and while recording. The next composed frame eases toward the new rect over about 120 ms; pass `smooth: false` to jump. Without a running compositor the update is stored and used the next time `'camera-screen'` starts. Invalid values throw and keep the previous layout.
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
recorder.setPictureInPicture({ position: { x: 0.72, y: 0.64 }, size: 0.25 });
|
|
849
|
+
recorder.setPictureInPicture({ position: 'top-left', smooth: false });
|
|
850
|
+
const layout = recorder.getPictureInPictureConfig();
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
`RecorderController` exposes the same `setPictureInPicture(update)` and `getPictureInPictureConfig()`.
|
|
854
|
+
|
|
855
|
+
The React hook `useVidtreoRecorder` accepts the same `startScreenWithoutCamera` and `pictureInPicture` options (`pictureInPicture` is the initial value) and exposes `actions.setPictureInPicture(update)`. It exposes `state.sourceType` and `state.cameraStream`. The prebuilt UI components do not offer these sources.
|
|
856
|
+
|
|
857
|
+
### System audio (advanced)
|
|
858
|
+
|
|
859
|
+
Set `captureSystemAudio: true` (also accepted by `useVidtreoRecorder` and `RecorderController.configureSources`) to record the computer's sound (calls, videos, other apps) together with the microphone when recording `'screen'` or `'camera-screen'`. Default `false`: the display stream's audio is requested with `audio: false` and any audio track is stopped, exactly as before.
|
|
860
|
+
|
|
861
|
+
- Screen pickers are requested with `audio: true`. Whether audio comes back depends on the browser and the user's choice (Chrome only offers it for tabs, or for the whole screen on Windows/ChromeOS; Electron apps return it from `setDisplayMediaRequestHandler` with `audio: 'loopback'`). Without a display audio track the recording falls back to the microphone, without an error.
|
|
862
|
+
- While recording, the microphone and the system audio are mixed into one audio track with Web Audio (`AudioContext` → one `MediaStreamAudioSourceNode` and `GainNode` per input → `MediaStreamAudioDestinationNode`). Web Audio resamples both inputs to the context rate. The context is created when the recording starts and closed when it stops.
|
|
863
|
+
- Mute only silences the microphone; the system audio keeps recording. Audio levels (`getAudioStreamForAnalysis`) keep showing the microphone only.
|
|
864
|
+
- Reselecting the screen, or switching between camera and screen sources, swaps the system audio input without stopping the recording. A recording started from the camera picks up system audio once it switches to a screen source.
|
|
865
|
+
- The preview never plays the system audio. If the microphone picks up the speakers, the system audio is recorded twice (once directly, once through the room). Browser echo cancellation (`echoCancellation`, on by default) only cancels audio the page itself plays, so recommend headphones during calls.
|
|
866
|
+
|
|
867
|
+
### Recording title
|
|
868
|
+
|
|
869
|
+
Call `setRecordingTitle(title)` before or during a recording to name the uploaded file `<title>.<ext>` instead of `recording-<timestamp>.<ext>`. The title is trimmed, whitespace is collapsed, control characters and `/ \ : * ? " < > |` are removed, leading dots are stripped and the result is capped at 120 characters. An empty result keeps the default name. The title is cleared once the recording's upload is queued, so set it again for the next recording. Pass `null` to clear it.
|
|
870
|
+
|
|
871
|
+
```typescript
|
|
872
|
+
recorder.setRecordingTitle('Demo – Q4 ventas');
|
|
873
|
+
await recorder.startRecording({}, 'camera');
|
|
874
|
+
const result = await recorder.stopRecording(); // uploads "Demo – Q4 ventas.mp4"
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
The same method exists on `RecorderController`, the React hook exposes it as `actions.setRecordingTitle(title)`, and `sanitizeRecordingTitle(title)` is exported to preview the sanitized name.
|
|
878
|
+
|
|
704
879
|
### Telemetry
|
|
705
880
|
|
|
706
881
|
The recorder automatically sends telemetry events to monitor SDK usage, detect issues, and improve the product. **Telemetry is always active** and cannot be disabled through configuration.
|
|
@@ -893,6 +1068,55 @@ await controller.initialize({
|
|
|
893
1068
|
});
|
|
894
1069
|
```
|
|
895
1070
|
|
|
1071
|
+
#### Native Camera Uploads
|
|
1072
|
+
|
|
1073
|
+
`NativeCameraHandler` validates a video picked with the device camera or gallery (`<input type="file" capture>`), transcodes it, and uploads it through the same persistent queue as recorded videos. UI packages use this single API for their native-camera mode.
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
import {
|
|
1077
|
+
ConfigService,
|
|
1078
|
+
NativeCameraHandler,
|
|
1079
|
+
VideoUploadService,
|
|
1080
|
+
getUploadErrorCode,
|
|
1081
|
+
} from '@vidtreo/recorder';
|
|
1082
|
+
|
|
1083
|
+
const handler = new NativeCameraHandler(
|
|
1084
|
+
{
|
|
1085
|
+
apiKey: 'your-api-key', // or getAccessToken: () => fetchToken()
|
|
1086
|
+
backendUrl: 'https://core.vidtreo.com',
|
|
1087
|
+
maxRecordingTime: 300000,
|
|
1088
|
+
maxFileSize: 500 * 1024 * 1024,
|
|
1089
|
+
userMetadata: { interviewId: '42' },
|
|
1090
|
+
},
|
|
1091
|
+
ConfigService.getInstance({ apiKey: 'your-api-key', backendUrl: 'https://core.vidtreo.com' }),
|
|
1092
|
+
new VideoUploadService()
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
await handler.startUploadQueue({
|
|
1096
|
+
onUploadProgress: (progress) => updateProgress(progress),
|
|
1097
|
+
onUploadProgressDetails: (details) => updatePhase(details.phase),
|
|
1098
|
+
onUploadRetry: (details) => showRetry(details.attempt),
|
|
1099
|
+
onUploadComplete: (result) => saveVideo(result),
|
|
1100
|
+
onUploadError: (error) => showError(getUploadErrorCode(error)),
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
const file = await handler.handleFileSelection(input.files[0]);
|
|
1104
|
+
const result = await handler.processAndUpload(
|
|
1105
|
+
(transcodeProgress) => updateTranscode(transcodeProgress),
|
|
1106
|
+
(uploadProgress) => updateProgress(uploadProgress),
|
|
1107
|
+
(details) => updatePhase(details.phase),
|
|
1108
|
+
(retry) => showRetry(retry.attempt)
|
|
1109
|
+
);
|
|
1110
|
+
console.log(result.recordingId, result.transferStatus, result.blob);
|
|
1111
|
+
|
|
1112
|
+
handler.destroy();
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
- `startUploadQueue(callbacks?)` opens the IndexedDB queue and immediately resumes uploads left over from a previous page load (same idempotency key, only missing parts are sent). Its callbacks receive events for those resumed uploads and for queue-level errors. It resolves to `false` when persistent storage is unavailable; uploads then go directly to the backend.
|
|
1116
|
+
- `processAndUpload(onTranscodeProgress, onUploadProgress, onUploadProgressDetails?, onUploadRetry?)` queues the transcoded file and resolves with a `NativeCameraUploadResult` (`RecordingUploadResult & { blob: Blob }`) once the upload is confirmed. It rejects only with a terminal error, after the queue has exhausted its retries or immediately for `upload.insufficient-credits`. Events for this upload go to the per-call callbacks, not to the `startUploadQueue` callbacks.
|
|
1117
|
+
- Calling `processAndUpload` without `startUploadQueue` opens the queue on demand.
|
|
1118
|
+
- `destroy()` stops the queue timers. An upload that is still pending stays in IndexedDB and resumes the next time a handler or recorder starts the queue.
|
|
1119
|
+
|
|
896
1120
|
#### Stream Management
|
|
897
1121
|
|
|
898
1122
|
```typescript
|