@vidtreo/recorder 1.7.9 → 1.7.10
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/dist/index.d.ts +22 -2
- package/dist/index.js +190 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -200,6 +200,8 @@ export {};
|
|
|
200
200
|
|
|
201
201
|
export {};
|
|
202
202
|
|
|
203
|
+
export {};
|
|
204
|
+
|
|
203
205
|
declare const ANSI_COLORS: {
|
|
204
206
|
readonly reset: "\u001B[0m";
|
|
205
207
|
readonly bright: "\u001B[1m";
|
|
@@ -1557,6 +1559,7 @@ import type { TranscodeConfig } from "../transcode/transcode-types";
|
|
|
1557
1559
|
import { type VisibilityInterval } from "../utils/tab-visibility-tracker";
|
|
1558
1560
|
export type StopRecordingResult = {
|
|
1559
1561
|
blob: Blob;
|
|
1562
|
+
captureFrameRate?: number;
|
|
1560
1563
|
tabVisibilityIntervals: VisibilityInterval[];
|
|
1561
1564
|
recordingStats?: StreamRecordingStats;
|
|
1562
1565
|
encoderAcceleration?: VideoHardwareAcceleration;
|
|
@@ -1587,6 +1590,7 @@ export declare class StreamRecordingState {
|
|
|
1587
1590
|
private mediaRecorderStopResolve;
|
|
1588
1591
|
private mediaRecorderStopReject;
|
|
1589
1592
|
private mediaRecorderError;
|
|
1593
|
+
private safeCaptureFrameRate;
|
|
1590
1594
|
private mediaRecorderErrorTimeoutId;
|
|
1591
1595
|
private readonly streamManager;
|
|
1592
1596
|
private readonly dependencies;
|
|
@@ -1611,6 +1615,7 @@ export declare class StreamRecordingState {
|
|
|
1611
1615
|
private clearBufferSizeInterval;
|
|
1612
1616
|
private resetRecordingState;
|
|
1613
1617
|
private startSafeCaptureRecording;
|
|
1618
|
+
private resolveEffectiveSafeCaptureFrameRate;
|
|
1614
1619
|
private applySafeCaptureTrackConstraints;
|
|
1615
1620
|
private resolveSafeCaptureVideoConstraints;
|
|
1616
1621
|
private stopSafeCaptureRecording;
|
|
@@ -1930,13 +1935,13 @@ export declare class RecorderController {
|
|
|
1930
1935
|
|
|
1931
1936
|
import type { CameraStreamManager } from "../stream/stream";
|
|
1932
1937
|
import { StreamProcessor } from "../stream/stream-processor";
|
|
1938
|
+
import { transcodeDeferredVideo } from "../transcode/deferred-video-transcoder";
|
|
1933
1939
|
import type { TranscodeConfig } from "../transcode/transcode-types";
|
|
1934
|
-
import { transcodeVideo } from "../transcode/video-transcoder";
|
|
1935
1940
|
export type RecordingStopResult = {
|
|
1936
1941
|
blob: Blob;
|
|
1937
1942
|
telemetryProperties: Record<string, unknown>;
|
|
1938
1943
|
};
|
|
1939
|
-
type TranscodeVideoDependency = typeof
|
|
1944
|
+
type TranscodeVideoDependency = typeof transcodeDeferredVideo;
|
|
1940
1945
|
type RecordingManagerDependencies = {
|
|
1941
1946
|
transcodeVideo: TranscodeVideoDependency;
|
|
1942
1947
|
};
|
|
@@ -1990,6 +1995,7 @@ export declare class RecordingManager {
|
|
|
1990
1995
|
cleanup(): void;
|
|
1991
1996
|
private resetRecordingState;
|
|
1992
1997
|
private resolveFinalRecordingBlob;
|
|
1998
|
+
private resolveDeferredCaptureFrameRate;
|
|
1993
1999
|
private resetPauseState;
|
|
1994
2000
|
private getOrCreateStreamProcessor;
|
|
1995
2001
|
private resolveRouteDecision;
|
|
@@ -3162,6 +3168,20 @@ export declare function getFileExtensionForOutputFormat(format: OutputFormat): s
|
|
|
3162
3168
|
export declare function transcodeVideo(input: TranscodeInput, config?: Partial<TranscodeConfig>, onProgress?: (progress: number) => void): Promise<TranscodeResult>;
|
|
3163
3169
|
export declare function transcodeVideoForNativeCamera(file: File, config?: Partial<TranscodeConfig>, onProgress?: (progress: number) => void): Promise<TranscodeResult>;
|
|
3164
3170
|
|
|
3171
|
+
export type VideoPacketSummary = {
|
|
3172
|
+
averagePacketRate: number;
|
|
3173
|
+
duration: number;
|
|
3174
|
+
maximumPacketGap: number;
|
|
3175
|
+
packetCount: number;
|
|
3176
|
+
};
|
|
3177
|
+
export type DeferredVideoStatsValidation = {
|
|
3178
|
+
output: VideoPacketSummary | null;
|
|
3179
|
+
source: VideoPacketSummary | null;
|
|
3180
|
+
targetFrameRate: number;
|
|
3181
|
+
};
|
|
3182
|
+
export declare function validateDeferredVideoStats(validation: DeferredVideoStatsValidation): void;
|
|
3183
|
+
export declare function transcodeDeferredVideo(input: Blob, config: TranscodeConfig, onProgress?: (progress: number) => void): Promise<TranscodeResult>;
|
|
3184
|
+
|
|
3165
3185
|
export type VidtreoRecorderConfig = {
|
|
3166
3186
|
apiKey: string;
|
|
3167
3187
|
apiUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -1890,6 +1890,9 @@ function createConversionOptions(config, optimizeForSpeed = false) {
|
|
|
1890
1890
|
if (config.codec !== undefined) {
|
|
1891
1891
|
video.codec = config.codec;
|
|
1892
1892
|
}
|
|
1893
|
+
if (config.latencyMode !== undefined) {
|
|
1894
|
+
video.latencyMode = config.latencyMode;
|
|
1895
|
+
}
|
|
1893
1896
|
if (optimizeForSpeed) {
|
|
1894
1897
|
video.bitrateMode = "variable";
|
|
1895
1898
|
video.latencyMode = "realtime";
|
|
@@ -5282,7 +5285,8 @@ var ANDROID_SAFE_CAPTURE_CONSTRAINTS = {
|
|
|
5282
5285
|
height: { ideal: 720, max: 720 },
|
|
5283
5286
|
frameRate: { ideal: 24, max: 24 }
|
|
5284
5287
|
};
|
|
5285
|
-
var
|
|
5288
|
+
var LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE = 20;
|
|
5289
|
+
var ANDROID_SAFE_CAPTURE_FRAME_RATE = 24;
|
|
5286
5290
|
var MEDIA_RECORDER_ERROR_FINALIZE_TIMEOUT_MS = 5000;
|
|
5287
5291
|
var ANDROID_POST_RECORDING_TRANSCODE_REASON = "android-post-recording-transcode";
|
|
5288
5292
|
var MEDIA_RECORDER_ERROR_CODE = "recording.mediarecorder-error";
|
|
@@ -5319,6 +5323,7 @@ class StreamRecordingState {
|
|
|
5319
5323
|
mediaRecorderStopResolve = null;
|
|
5320
5324
|
mediaRecorderStopReject = null;
|
|
5321
5325
|
mediaRecorderError = null;
|
|
5326
|
+
safeCaptureFrameRate = null;
|
|
5322
5327
|
mediaRecorderErrorTimeoutId = null;
|
|
5323
5328
|
streamManager;
|
|
5324
5329
|
dependencies;
|
|
@@ -5496,6 +5501,7 @@ class StreamRecordingState {
|
|
|
5496
5501
|
logger.debug("[StreamRecordingState] Finalizing MediaRecorder capture");
|
|
5497
5502
|
result = {
|
|
5498
5503
|
blob: safeCaptureResult.blob,
|
|
5504
|
+
captureFrameRate: safeCaptureResult.captureFrameRate,
|
|
5499
5505
|
mediaRecorderDiagnostics: safeCaptureResult.diagnostics
|
|
5500
5506
|
};
|
|
5501
5507
|
} else {
|
|
@@ -5513,6 +5519,7 @@ class StreamRecordingState {
|
|
|
5513
5519
|
});
|
|
5514
5520
|
return {
|
|
5515
5521
|
blob: result.blob,
|
|
5522
|
+
captureFrameRate: result.captureFrameRate,
|
|
5516
5523
|
tabVisibilityIntervals,
|
|
5517
5524
|
recordingStats: result.recordingStats,
|
|
5518
5525
|
encoderAcceleration: result.encoderAcceleration,
|
|
@@ -5644,6 +5651,7 @@ class StreamRecordingState {
|
|
|
5644
5651
|
});
|
|
5645
5652
|
}
|
|
5646
5653
|
await this.applySafeCaptureTrackConstraints(mediaStream);
|
|
5654
|
+
this.safeCaptureFrameRate = this.resolveEffectiveSafeCaptureFrameRate(mediaStream, config);
|
|
5647
5655
|
this.mediaRecorderChunks = [];
|
|
5648
5656
|
this.mediaRecorderError = null;
|
|
5649
5657
|
const preferredMimeType = this.resolvePreferredSafeCaptureMimeType(config);
|
|
@@ -5672,8 +5680,18 @@ class StreamRecordingState {
|
|
|
5672
5680
|
const mimeType = recorder.mimeType || this.resolveSafeCaptureMimeType();
|
|
5673
5681
|
const blob = new Blob(this.mediaRecorderChunks, { type: mimeType });
|
|
5674
5682
|
const diagnostics = this.buildMediaRecorderDiagnostics(recorder);
|
|
5683
|
+
const captureFrameRate = this.safeCaptureFrameRate;
|
|
5684
|
+
if (captureFrameRate === null) {
|
|
5685
|
+
this.mediaRecorderStopReject?.(new Error("Safe capture frame rate is unavailable"));
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
diagnostics.mediaRecorderCaptureFrameRate = captureFrameRate;
|
|
5675
5689
|
if (blob.size > 0) {
|
|
5676
|
-
this.mediaRecorderStopResolve?.({
|
|
5690
|
+
this.mediaRecorderStopResolve?.({
|
|
5691
|
+
blob,
|
|
5692
|
+
captureFrameRate,
|
|
5693
|
+
diagnostics
|
|
5694
|
+
});
|
|
5677
5695
|
return;
|
|
5678
5696
|
}
|
|
5679
5697
|
this.mediaRecorderStopReject?.(this.createEmptyMediaRecorderError(recorder));
|
|
@@ -5681,7 +5699,27 @@ class StreamRecordingState {
|
|
|
5681
5699
|
recorder.onerror = (event) => {
|
|
5682
5700
|
this.mediaRecorderError = this.createMediaRecorderError(event, recorder);
|
|
5683
5701
|
};
|
|
5684
|
-
recorder.start(
|
|
5702
|
+
recorder.start();
|
|
5703
|
+
}
|
|
5704
|
+
resolveEffectiveSafeCaptureFrameRate(mediaStream, config) {
|
|
5705
|
+
const videoTrack = mediaStream.getVideoTracks()[0];
|
|
5706
|
+
if (videoTrack && typeof videoTrack.getSettings === "function") {
|
|
5707
|
+
const frameRate = videoTrack.getSettings().frameRate;
|
|
5708
|
+
if (typeof frameRate === "number" && Number.isFinite(frameRate) && frameRate > 0) {
|
|
5709
|
+
return frameRate;
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
const reasonCodes = this.activeRouteDecision.reasonCodes;
|
|
5713
|
+
if (reasonCodes.includes(ANDROID_POST_RECORDING_TRANSCODE_REASON)) {
|
|
5714
|
+
return ANDROID_SAFE_CAPTURE_FRAME_RATE;
|
|
5715
|
+
}
|
|
5716
|
+
if (reasonCodes.includes("insufficient-device-memory") || reasonCodes.includes("insufficient-hardware-concurrency") || reasonCodes.includes("encoding-capability-unsupported") || reasonCodes.includes("encoding-not-smooth") || reasonCodes.includes("encoding-not-power-efficient")) {
|
|
5717
|
+
return LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE;
|
|
5718
|
+
}
|
|
5719
|
+
if (typeof config.fps === "number" && Number.isFinite(config.fps) && config.fps > 0) {
|
|
5720
|
+
return config.fps;
|
|
5721
|
+
}
|
|
5722
|
+
return LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE;
|
|
5685
5723
|
}
|
|
5686
5724
|
async applySafeCaptureTrackConstraints(mediaStream) {
|
|
5687
5725
|
const constraints = this.resolveSafeCaptureVideoConstraints();
|
|
@@ -5726,6 +5764,7 @@ class StreamRecordingState {
|
|
|
5726
5764
|
this.mediaRecorderStopResolve = null;
|
|
5727
5765
|
this.mediaRecorderStopReject = null;
|
|
5728
5766
|
this.mediaRecorderError = null;
|
|
5767
|
+
this.safeCaptureFrameRate = null;
|
|
5729
5768
|
this.clearMediaRecorderErrorTimeout();
|
|
5730
5769
|
this.activeRouteDecision = DEFAULT_ROUTE_DECISION;
|
|
5731
5770
|
}
|
|
@@ -6315,7 +6354,7 @@ function resolveDeviceError(input) {
|
|
|
6315
6354
|
// package.json
|
|
6316
6355
|
var package_default = {
|
|
6317
6356
|
name: "@vidtreo/recorder",
|
|
6318
|
-
version: "1.7.
|
|
6357
|
+
version: "1.7.10",
|
|
6319
6358
|
type: "module",
|
|
6320
6359
|
description: "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
|
|
6321
6360
|
main: "./dist/index.js",
|
|
@@ -22751,6 +22790,136 @@ class StreamProcessor {
|
|
|
22751
22790
|
}
|
|
22752
22791
|
}
|
|
22753
22792
|
|
|
22793
|
+
// src/core/transcode/deferred-video-transcoder.ts
|
|
22794
|
+
import { ALL_FORMATS as ALL_FORMATS2, BlobSource as BlobSource3, EncodedPacketSink, Input as Input3 } from "mediabunny";
|
|
22795
|
+
var MINIMUM_FRAME_DENSITY_RATIO = 0.9;
|
|
22796
|
+
var MAXIMUM_DURATION_DIFFERENCE_RATIO = 0.05;
|
|
22797
|
+
var MINIMUM_DURATION_TOLERANCE_SECONDS = 0.25;
|
|
22798
|
+
var MAXIMUM_FRAME_GAP_MULTIPLIER = 5;
|
|
22799
|
+
var MINIMUM_FRAME_GAP_TOLERANCE_SECONDS = 0.25;
|
|
22800
|
+
var INTEGRITY_ERROR_CODE = "recording.deferred-transcode-integrity";
|
|
22801
|
+
function createIntegrityError(message) {
|
|
22802
|
+
const error = new Error(message);
|
|
22803
|
+
error.name = "DeferredTranscodeIntegrityError";
|
|
22804
|
+
error.code = INTEGRITY_ERROR_CODE;
|
|
22805
|
+
error.isRecoverable = true;
|
|
22806
|
+
return error;
|
|
22807
|
+
}
|
|
22808
|
+
function validatePacketSummary(summary, label) {
|
|
22809
|
+
if (!Number.isFinite(summary.averagePacketRate)) {
|
|
22810
|
+
throw createIntegrityError(`${label} video packet rate is invalid`);
|
|
22811
|
+
}
|
|
22812
|
+
if (!Number.isFinite(summary.duration)) {
|
|
22813
|
+
throw createIntegrityError(`${label} video duration is invalid`);
|
|
22814
|
+
}
|
|
22815
|
+
if (!Number.isFinite(summary.maximumPacketGap)) {
|
|
22816
|
+
throw createIntegrityError(`${label} video maximum packet gap is invalid`);
|
|
22817
|
+
}
|
|
22818
|
+
if (summary.averagePacketRate <= 0 || summary.duration <= 0) {
|
|
22819
|
+
throw createIntegrityError(`${label} video packet statistics are empty`);
|
|
22820
|
+
}
|
|
22821
|
+
if (summary.maximumPacketGap < 0) {
|
|
22822
|
+
throw createIntegrityError(`${label} video maximum packet gap is invalid`);
|
|
22823
|
+
}
|
|
22824
|
+
if (!Number.isInteger(summary.packetCount) || summary.packetCount <= 0) {
|
|
22825
|
+
throw createIntegrityError(`${label} video packet count is invalid`);
|
|
22826
|
+
}
|
|
22827
|
+
}
|
|
22828
|
+
function validateFrameCadence(summary, label, targetFrameRate) {
|
|
22829
|
+
const minimumPacketRate = targetFrameRate * MINIMUM_FRAME_DENSITY_RATIO;
|
|
22830
|
+
if (summary.averagePacketRate < minimumPacketRate) {
|
|
22831
|
+
throw createIntegrityError(`${label} video frame density is below the capture target`);
|
|
22832
|
+
}
|
|
22833
|
+
const maximumPacketGap = Math.max(MINIMUM_FRAME_GAP_TOLERANCE_SECONDS, MAXIMUM_FRAME_GAP_MULTIPLIER / targetFrameRate);
|
|
22834
|
+
if (summary.maximumPacketGap > maximumPacketGap) {
|
|
22835
|
+
throw createIntegrityError(`${label} video contains a material frame gap`);
|
|
22836
|
+
}
|
|
22837
|
+
}
|
|
22838
|
+
function validateDeferredVideoStats(validation) {
|
|
22839
|
+
if (!Number.isFinite(validation.targetFrameRate) || validation.targetFrameRate <= 0) {
|
|
22840
|
+
throw createIntegrityError("Deferred transcode requires a valid capture frame rate");
|
|
22841
|
+
}
|
|
22842
|
+
if (!(validation.source && validation.output)) {
|
|
22843
|
+
throw createIntegrityError("Deferred transcode source or output has no primary video track");
|
|
22844
|
+
}
|
|
22845
|
+
validatePacketSummary(validation.source, "Source");
|
|
22846
|
+
validatePacketSummary(validation.output, "Output");
|
|
22847
|
+
validateFrameCadence(validation.source, "Source", validation.targetFrameRate);
|
|
22848
|
+
validateFrameCadence(validation.output, "Output", validation.targetFrameRate);
|
|
22849
|
+
const durationDifference = Math.abs(validation.source.duration - validation.output.duration);
|
|
22850
|
+
const durationTolerance = Math.max(MINIMUM_DURATION_TOLERANCE_SECONDS, validation.source.duration * MAXIMUM_DURATION_DIFFERENCE_RATIO);
|
|
22851
|
+
if (durationDifference > durationTolerance) {
|
|
22852
|
+
throw createIntegrityError("Deferred transcode output duration differs materially from source");
|
|
22853
|
+
}
|
|
22854
|
+
const minimumPacketRate = validation.targetFrameRate * MINIMUM_FRAME_DENSITY_RATIO;
|
|
22855
|
+
const minimumPacketCount = Math.floor(validation.source.duration * minimumPacketRate);
|
|
22856
|
+
if (validation.output.averagePacketRate < minimumPacketRate || validation.output.packetCount < minimumPacketCount) {
|
|
22857
|
+
throw createIntegrityError("Deferred transcode output lost material video frame density");
|
|
22858
|
+
}
|
|
22859
|
+
}
|
|
22860
|
+
async function inspectVideoPackets(blob) {
|
|
22861
|
+
const input = new Input3({
|
|
22862
|
+
formats: ALL_FORMATS2,
|
|
22863
|
+
source: new BlobSource3(blob)
|
|
22864
|
+
});
|
|
22865
|
+
const videoTrack = await input.getPrimaryVideoTrack();
|
|
22866
|
+
if (videoTrack === null) {
|
|
22867
|
+
input.dispose();
|
|
22868
|
+
return null;
|
|
22869
|
+
}
|
|
22870
|
+
try {
|
|
22871
|
+
const packetSink = new EncodedPacketSink(videoTrack);
|
|
22872
|
+
const timestamps = [];
|
|
22873
|
+
let firstTimestamp = Number.POSITIVE_INFINITY;
|
|
22874
|
+
let lastEndTimestamp = Number.NEGATIVE_INFINITY;
|
|
22875
|
+
for await (const packet of packetSink.packets(undefined, undefined, {
|
|
22876
|
+
metadataOnly: true
|
|
22877
|
+
})) {
|
|
22878
|
+
timestamps.push(packet.timestamp);
|
|
22879
|
+
firstTimestamp = Math.min(firstTimestamp, packet.timestamp);
|
|
22880
|
+
lastEndTimestamp = Math.max(lastEndTimestamp, packet.timestamp + packet.duration);
|
|
22881
|
+
}
|
|
22882
|
+
timestamps.sort((left, right) => left - right);
|
|
22883
|
+
let maximumPacketGap = 0;
|
|
22884
|
+
for (let index = 1;index < timestamps.length; index += 1) {
|
|
22885
|
+
maximumPacketGap = Math.max(maximumPacketGap, timestamps[index] - timestamps[index - 1]);
|
|
22886
|
+
}
|
|
22887
|
+
const duration = lastEndTimestamp - firstTimestamp;
|
|
22888
|
+
const packetCount = timestamps.length;
|
|
22889
|
+
const averagePacketRate = packetCount > 0 ? packetCount / duration : 0;
|
|
22890
|
+
return {
|
|
22891
|
+
averagePacketRate,
|
|
22892
|
+
duration,
|
|
22893
|
+
maximumPacketGap,
|
|
22894
|
+
packetCount
|
|
22895
|
+
};
|
|
22896
|
+
} finally {
|
|
22897
|
+
input.dispose();
|
|
22898
|
+
}
|
|
22899
|
+
}
|
|
22900
|
+
async function inspectDeferredVideo(blob, label) {
|
|
22901
|
+
return await inspectVideoPackets(blob).catch((error) => {
|
|
22902
|
+
const message = extractErrorMessage(error);
|
|
22903
|
+
throw createIntegrityError(`Could not inspect ${label} deferred video: ${message}`);
|
|
22904
|
+
});
|
|
22905
|
+
}
|
|
22906
|
+
async function transcodeDeferredVideo(input, config, onProgress) {
|
|
22907
|
+
const targetFrameRate = config.fps;
|
|
22908
|
+
if (typeof targetFrameRate !== "number" || !Number.isFinite(targetFrameRate) || targetFrameRate <= 0) {
|
|
22909
|
+
throw createIntegrityError("Deferred transcode requires a valid capture frame rate");
|
|
22910
|
+
}
|
|
22911
|
+
const source = await inspectDeferredVideo(input, "source");
|
|
22912
|
+
validateDeferredVideoStats({
|
|
22913
|
+
output: source,
|
|
22914
|
+
source,
|
|
22915
|
+
targetFrameRate
|
|
22916
|
+
});
|
|
22917
|
+
const result = await transcodeVideo(input, config, onProgress);
|
|
22918
|
+
const output = await inspectDeferredVideo(result.blob, "output");
|
|
22919
|
+
validateDeferredVideoStats({ output, source, targetFrameRate });
|
|
22920
|
+
return result;
|
|
22921
|
+
}
|
|
22922
|
+
|
|
22754
22923
|
// src/core/recording/recording-manager.ts
|
|
22755
22924
|
var DEFAULT_COUNTDOWN_DURATION = 5000;
|
|
22756
22925
|
var MILLISECONDS_PER_SECOND4 = 1000;
|
|
@@ -22759,12 +22928,15 @@ var RECORDING_TIMER_INTERVAL = 1000;
|
|
|
22759
22928
|
var RECORDING_STATE_RECORDING = "recording";
|
|
22760
22929
|
var RECORDING_STATE_IDLE = "idle";
|
|
22761
22930
|
var RECORDING_STATE_COUNTDOWN = "countdown";
|
|
22931
|
+
var LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE2 = 20;
|
|
22932
|
+
var ANDROID_SAFE_CAPTURE_FRAME_RATE2 = 24;
|
|
22933
|
+
var ANDROID_POST_RECORDING_TRANSCODE_REASON2 = "android-post-recording-transcode";
|
|
22762
22934
|
var DEFAULT_ROUTE_DECISION2 = {
|
|
22763
22935
|
route: "local-webcodecs",
|
|
22764
22936
|
reasonCodes: ["webcodecs-supported", "target-codec-supported"]
|
|
22765
22937
|
};
|
|
22766
22938
|
var DEFAULT_RECORDING_MANAGER_DEPENDENCIES = {
|
|
22767
|
-
transcodeVideo
|
|
22939
|
+
transcodeVideo: transcodeDeferredVideo
|
|
22768
22940
|
};
|
|
22769
22941
|
|
|
22770
22942
|
class RecordingManager {
|
|
@@ -22999,7 +23171,7 @@ class RecordingManager {
|
|
|
22999
23171
|
this.callbacks.onStopAudioTracking();
|
|
23000
23172
|
logger.debug("[RecordingManager] Stopping recording in stream manager");
|
|
23001
23173
|
const stopResult = await this.streamManager.stopRecording();
|
|
23002
|
-
const finalBlob = await this.resolveFinalRecordingBlob(stopResult.blob);
|
|
23174
|
+
const finalBlob = await this.resolveFinalRecordingBlob(stopResult.blob, stopResult.captureFrameRate);
|
|
23003
23175
|
logger.info("[RecordingManager] Recording stopped, blob size:", finalBlob.size);
|
|
23004
23176
|
this.recordingState = RECORDING_STATE_IDLE;
|
|
23005
23177
|
this.callbacks.onStateChange(this.recordingState);
|
|
@@ -23101,7 +23273,7 @@ class RecordingManager {
|
|
|
23101
23273
|
this.recordingStartTime = null;
|
|
23102
23274
|
this.maxTimeRemaining = null;
|
|
23103
23275
|
}
|
|
23104
|
-
async resolveFinalRecordingBlob(blob) {
|
|
23276
|
+
async resolveFinalRecordingBlob(blob, captureFrameRate) {
|
|
23105
23277
|
if (this.activeRouteDecision.route !== "safe-capture-post-recording-transcode") {
|
|
23106
23278
|
return blob;
|
|
23107
23279
|
}
|
|
@@ -23110,7 +23282,8 @@ class RecordingManager {
|
|
|
23110
23282
|
}
|
|
23111
23283
|
const transcodeConfig = {
|
|
23112
23284
|
...this.activeRecordingConfig,
|
|
23113
|
-
fps:
|
|
23285
|
+
fps: this.resolveDeferredCaptureFrameRate(captureFrameRate),
|
|
23286
|
+
latencyMode: "quality"
|
|
23114
23287
|
};
|
|
23115
23288
|
this.callbacks.onTranscodingProgress?.(0);
|
|
23116
23289
|
const result = await this.dependencies.transcodeVideo(blob, transcodeConfig, (progress) => {
|
|
@@ -23119,6 +23292,15 @@ class RecordingManager {
|
|
|
23119
23292
|
this.callbacks.onTranscodingProgress?.(1);
|
|
23120
23293
|
return result.blob;
|
|
23121
23294
|
}
|
|
23295
|
+
resolveDeferredCaptureFrameRate(captureFrameRate) {
|
|
23296
|
+
if (typeof captureFrameRate === "number" && Number.isFinite(captureFrameRate) && captureFrameRate > 0) {
|
|
23297
|
+
return captureFrameRate;
|
|
23298
|
+
}
|
|
23299
|
+
if (this.activeRouteDecision.reasonCodes.includes(ANDROID_POST_RECORDING_TRANSCODE_REASON2)) {
|
|
23300
|
+
return ANDROID_SAFE_CAPTURE_FRAME_RATE2;
|
|
23301
|
+
}
|
|
23302
|
+
return LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE2;
|
|
23303
|
+
}
|
|
23122
23304
|
resetPauseState() {
|
|
23123
23305
|
this.isPaused = false;
|
|
23124
23306
|
this.pauseStartTime = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
|
|
6
6
|
"main": "./dist/index.js",
|