@vidtreo/recorder 1.7.10 → 1.7.12
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 +3024 -2977
- package/dist/index.js +685 -178
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5287,6 +5287,7 @@ var ANDROID_SAFE_CAPTURE_CONSTRAINTS = {
|
|
|
5287
5287
|
};
|
|
5288
5288
|
var LOW_RESOURCE_SAFE_CAPTURE_FRAME_RATE = 20;
|
|
5289
5289
|
var ANDROID_SAFE_CAPTURE_FRAME_RATE = 24;
|
|
5290
|
+
var SAFE_CAPTURE_TIMESLICE_MS = 1000;
|
|
5290
5291
|
var MEDIA_RECORDER_ERROR_FINALIZE_TIMEOUT_MS = 5000;
|
|
5291
5292
|
var ANDROID_POST_RECORDING_TRANSCODE_REASON = "android-post-recording-transcode";
|
|
5292
5293
|
var MEDIA_RECORDER_ERROR_CODE = "recording.mediarecorder-error";
|
|
@@ -5324,6 +5325,11 @@ class StreamRecordingState {
|
|
|
5324
5325
|
mediaRecorderStopReject = null;
|
|
5325
5326
|
mediaRecorderError = null;
|
|
5326
5327
|
safeCaptureFrameRate = null;
|
|
5328
|
+
safeCaptureStartTime = null;
|
|
5329
|
+
safeCaptureExpectedDurationSeconds = null;
|
|
5330
|
+
mediaRecorderRequestedMimeType = null;
|
|
5331
|
+
mediaRecorderGeneration = 0;
|
|
5332
|
+
isSafeCaptureSettled = false;
|
|
5327
5333
|
mediaRecorderErrorTimeoutId = null;
|
|
5328
5334
|
streamManager;
|
|
5329
5335
|
dependencies;
|
|
@@ -5466,7 +5472,8 @@ class StreamRecordingState {
|
|
|
5466
5472
|
this.startRecordingTimer();
|
|
5467
5473
|
}
|
|
5468
5474
|
async stopRecording() {
|
|
5469
|
-
const
|
|
5475
|
+
const stopTimestamp = this.dependencies.getCurrentTimestamp();
|
|
5476
|
+
const recordingElapsedSeconds = this.resolveActiveRecordingDurationSeconds(stopTimestamp);
|
|
5470
5477
|
logger.debug("[StreamRecordingState] stopRecording called", {
|
|
5471
5478
|
hasStreamProcessor: !!this.streamProcessor,
|
|
5472
5479
|
isRecording: this.isRecording(),
|
|
@@ -5497,11 +5504,12 @@ class StreamRecordingState {
|
|
|
5497
5504
|
}
|
|
5498
5505
|
let result;
|
|
5499
5506
|
if (this.activeRouteDecision.route === "safe-capture-post-recording-transcode") {
|
|
5500
|
-
const safeCaptureResult = await this.stopSafeCaptureRecording();
|
|
5507
|
+
const safeCaptureResult = await this.stopSafeCaptureRecording(recordingElapsedSeconds);
|
|
5501
5508
|
logger.debug("[StreamRecordingState] Finalizing MediaRecorder capture");
|
|
5502
5509
|
result = {
|
|
5503
5510
|
blob: safeCaptureResult.blob,
|
|
5504
5511
|
captureFrameRate: safeCaptureResult.captureFrameRate,
|
|
5512
|
+
expectedDurationSeconds: safeCaptureResult.expectedDurationSeconds,
|
|
5505
5513
|
mediaRecorderDiagnostics: safeCaptureResult.diagnostics
|
|
5506
5514
|
};
|
|
5507
5515
|
} else {
|
|
@@ -5520,6 +5528,7 @@ class StreamRecordingState {
|
|
|
5520
5528
|
return {
|
|
5521
5529
|
blob: result.blob,
|
|
5522
5530
|
captureFrameRate: result.captureFrameRate,
|
|
5531
|
+
expectedDurationSeconds: result.expectedDurationSeconds,
|
|
5523
5532
|
tabVisibilityIntervals,
|
|
5524
5533
|
recordingStats: result.recordingStats,
|
|
5525
5534
|
encoderAcceleration: result.encoderAcceleration,
|
|
@@ -5544,7 +5553,7 @@ class StreamRecordingState {
|
|
|
5544
5553
|
resumeRecording() {
|
|
5545
5554
|
if (this.pauseStartTime !== null) {
|
|
5546
5555
|
const pausedDuration = this.dependencies.getCurrentTimestamp() - this.pauseStartTime;
|
|
5547
|
-
this.totalPausedTime += pausedDuration;
|
|
5556
|
+
this.totalPausedTime += Math.max(0, pausedDuration);
|
|
5548
5557
|
this.pauseStartTime = null;
|
|
5549
5558
|
}
|
|
5550
5559
|
if (this.tabVisibilityTracker) {
|
|
@@ -5639,6 +5648,18 @@ class StreamRecordingState {
|
|
|
5639
5648
|
this.totalPausedTime = 0;
|
|
5640
5649
|
this.pauseStartTime = null;
|
|
5641
5650
|
}
|
|
5651
|
+
resolveActiveRecordingDurationSeconds(timestamp) {
|
|
5652
|
+
let startTime = this.recordingStartTime;
|
|
5653
|
+
if (this.safeCaptureStartTime !== null) {
|
|
5654
|
+
startTime = this.safeCaptureStartTime;
|
|
5655
|
+
}
|
|
5656
|
+
let pausedTime = this.totalPausedTime;
|
|
5657
|
+
if (this.pauseStartTime !== null) {
|
|
5658
|
+
pausedTime += Math.max(0, timestamp - this.pauseStartTime);
|
|
5659
|
+
}
|
|
5660
|
+
const activeDuration = Math.max(0, timestamp - startTime - pausedTime);
|
|
5661
|
+
return activeDuration / MILLISECONDS_PER_SECOND2;
|
|
5662
|
+
}
|
|
5642
5663
|
async startSafeCaptureRecording(mediaStream, config) {
|
|
5643
5664
|
if (typeof MediaRecorder === "undefined") {
|
|
5644
5665
|
throw createUnsupportedRouteError({
|
|
@@ -5654,9 +5675,13 @@ class StreamRecordingState {
|
|
|
5654
5675
|
this.safeCaptureFrameRate = this.resolveEffectiveSafeCaptureFrameRate(mediaStream, config);
|
|
5655
5676
|
this.mediaRecorderChunks = [];
|
|
5656
5677
|
this.mediaRecorderError = null;
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
const
|
|
5678
|
+
this.safeCaptureExpectedDurationSeconds = null;
|
|
5679
|
+
this.isSafeCaptureSettled = false;
|
|
5680
|
+
const generation = this.mediaRecorderGeneration + 1;
|
|
5681
|
+
this.mediaRecorderGeneration = generation;
|
|
5682
|
+
const selection = await this.createSafeCaptureRecorder(mediaStream);
|
|
5683
|
+
const recorder = selection.recorder;
|
|
5684
|
+
this.mediaRecorderRequestedMimeType = selection.requestedMimeType === undefined ? null : selection.requestedMimeType;
|
|
5660
5685
|
this.mediaRecorder = recorder;
|
|
5661
5686
|
this.mediaRecorderStopPromise = new Promise((resolve, reject) => {
|
|
5662
5687
|
this.mediaRecorderStopResolve = resolve;
|
|
@@ -5666,40 +5691,76 @@ class StreamRecordingState {
|
|
|
5666
5691
|
return;
|
|
5667
5692
|
});
|
|
5668
5693
|
recorder.ondataavailable = (event) => {
|
|
5694
|
+
if (!this.isActiveSafeCaptureGeneration(generation)) {
|
|
5695
|
+
return;
|
|
5696
|
+
}
|
|
5669
5697
|
if (event.data.size > 0) {
|
|
5670
5698
|
this.mediaRecorderChunks.push(event.data);
|
|
5671
5699
|
}
|
|
5672
5700
|
};
|
|
5673
5701
|
recorder.onstop = () => {
|
|
5702
|
+
if (!this.isActiveSafeCaptureGeneration(generation)) {
|
|
5703
|
+
return;
|
|
5704
|
+
}
|
|
5705
|
+
this.isSafeCaptureSettled = true;
|
|
5674
5706
|
this.clearMediaRecorderErrorTimeout();
|
|
5675
5707
|
if (this.mediaRecorderError !== null) {
|
|
5676
5708
|
this.updateMediaRecorderErrorDiagnostics(recorder);
|
|
5677
5709
|
this.mediaRecorderStopReject?.(this.mediaRecorderError);
|
|
5678
5710
|
return;
|
|
5679
5711
|
}
|
|
5680
|
-
const mimeType =
|
|
5712
|
+
const mimeType = this.resolveRecordedMimeType(recorder);
|
|
5681
5713
|
const blob = new Blob(this.mediaRecorderChunks, { type: mimeType });
|
|
5682
5714
|
const diagnostics = this.buildMediaRecorderDiagnostics(recorder);
|
|
5683
5715
|
const captureFrameRate = this.safeCaptureFrameRate;
|
|
5716
|
+
let expectedDurationSeconds = this.safeCaptureExpectedDurationSeconds;
|
|
5717
|
+
if (expectedDurationSeconds === null) {
|
|
5718
|
+
expectedDurationSeconds = this.resolveActiveRecordingDurationSeconds(this.dependencies.getCurrentTimestamp());
|
|
5719
|
+
}
|
|
5684
5720
|
if (captureFrameRate === null) {
|
|
5685
5721
|
this.mediaRecorderStopReject?.(new Error("Safe capture frame rate is unavailable"));
|
|
5686
5722
|
return;
|
|
5687
5723
|
}
|
|
5688
5724
|
diagnostics.mediaRecorderCaptureFrameRate = captureFrameRate;
|
|
5725
|
+
diagnostics.mediaRecorderExpectedDurationSeconds = expectedDurationSeconds;
|
|
5689
5726
|
if (blob.size > 0) {
|
|
5690
5727
|
this.mediaRecorderStopResolve?.({
|
|
5691
5728
|
blob,
|
|
5692
5729
|
captureFrameRate,
|
|
5693
|
-
diagnostics
|
|
5730
|
+
diagnostics,
|
|
5731
|
+
expectedDurationSeconds
|
|
5694
5732
|
});
|
|
5695
5733
|
return;
|
|
5696
5734
|
}
|
|
5697
5735
|
this.mediaRecorderStopReject?.(this.createEmptyMediaRecorderError(recorder));
|
|
5698
5736
|
};
|
|
5699
5737
|
recorder.onerror = (event) => {
|
|
5738
|
+
if (!this.isActiveSafeCaptureGeneration(generation)) {
|
|
5739
|
+
return;
|
|
5740
|
+
}
|
|
5700
5741
|
this.mediaRecorderError = this.createMediaRecorderError(event, recorder);
|
|
5701
5742
|
};
|
|
5702
|
-
|
|
5743
|
+
this.safeCaptureStartTime = this.dependencies.getCurrentTimestamp();
|
|
5744
|
+
recorder.start(SAFE_CAPTURE_TIMESLICE_MS);
|
|
5745
|
+
}
|
|
5746
|
+
isActiveSafeCaptureGeneration(generation) {
|
|
5747
|
+
return generation === this.mediaRecorderGeneration && !this.isSafeCaptureSettled;
|
|
5748
|
+
}
|
|
5749
|
+
async createSafeCaptureRecorder(mediaStream, candidateIndex = 0) {
|
|
5750
|
+
const candidateMimeTypes = this.resolveSafeCaptureMimeTypeCandidates();
|
|
5751
|
+
const requestedMimeType = candidateMimeTypes[candidateIndex];
|
|
5752
|
+
if (requestedMimeType === undefined) {
|
|
5753
|
+
return {
|
|
5754
|
+
recorder: new MediaRecorder(mediaStream)
|
|
5755
|
+
};
|
|
5756
|
+
}
|
|
5757
|
+
const recorder = await Promise.resolve().then(() => new MediaRecorder(mediaStream, {
|
|
5758
|
+
mimeType: requestedMimeType
|
|
5759
|
+
})).catch(() => null);
|
|
5760
|
+
if (recorder !== null) {
|
|
5761
|
+
return { recorder, requestedMimeType };
|
|
5762
|
+
}
|
|
5763
|
+
return await this.createSafeCaptureRecorder(mediaStream, candidateIndex + 1);
|
|
5703
5764
|
}
|
|
5704
5765
|
resolveEffectiveSafeCaptureFrameRate(mediaStream, config) {
|
|
5705
5766
|
const videoTrack = mediaStream.getVideoTracks()[0];
|
|
@@ -5728,6 +5789,9 @@ class StreamRecordingState {
|
|
|
5728
5789
|
}
|
|
5729
5790
|
const videoTracks = mediaStream.getVideoTracks();
|
|
5730
5791
|
await Promise.all(videoTracks.map(async (track) => {
|
|
5792
|
+
if (typeof track.applyConstraints !== "function") {
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5731
5795
|
try {
|
|
5732
5796
|
await track.applyConstraints(constraints);
|
|
5733
5797
|
} catch (error) {
|
|
@@ -5745,36 +5809,62 @@ class StreamRecordingState {
|
|
|
5745
5809
|
}
|
|
5746
5810
|
return null;
|
|
5747
5811
|
}
|
|
5748
|
-
async stopSafeCaptureRecording() {
|
|
5812
|
+
async stopSafeCaptureRecording(expectedDurationSeconds) {
|
|
5749
5813
|
const recorder = this.mediaRecorder;
|
|
5750
5814
|
const stopPromise = this.mediaRecorderStopPromise;
|
|
5751
5815
|
if (!(recorder && stopPromise)) {
|
|
5752
5816
|
throw new Error("Safe capture recorder was not started");
|
|
5753
5817
|
}
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
this.
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5818
|
+
this.safeCaptureExpectedDurationSeconds = expectedDurationSeconds;
|
|
5819
|
+
return await Promise.resolve().then(() => {
|
|
5820
|
+
if (recorder.state !== "inactive") {
|
|
5821
|
+
recorder.stop();
|
|
5822
|
+
}
|
|
5823
|
+
this.scheduleMediaRecorderErrorFinalizeTimeout(recorder);
|
|
5824
|
+
return stopPromise;
|
|
5825
|
+
}).then((result) => {
|
|
5826
|
+
result.expectedDurationSeconds = expectedDurationSeconds;
|
|
5827
|
+
result.diagnostics.mediaRecorderExpectedDurationSeconds = expectedDurationSeconds;
|
|
5828
|
+
return result;
|
|
5829
|
+
}).finally(() => {
|
|
5830
|
+
this.cleanupSafeCaptureRecording();
|
|
5831
|
+
});
|
|
5832
|
+
}
|
|
5833
|
+
cleanupSafeCaptureRecording() {
|
|
5834
|
+
this.mediaRecorder = null;
|
|
5835
|
+
this.mediaRecorderChunks = [];
|
|
5836
|
+
this.mediaRecorderStopPromise = null;
|
|
5837
|
+
this.mediaRecorderStopResolve = null;
|
|
5838
|
+
this.mediaRecorderStopReject = null;
|
|
5839
|
+
this.mediaRecorderError = null;
|
|
5840
|
+
this.safeCaptureFrameRate = null;
|
|
5841
|
+
this.safeCaptureStartTime = null;
|
|
5842
|
+
this.safeCaptureExpectedDurationSeconds = null;
|
|
5843
|
+
this.mediaRecorderRequestedMimeType = null;
|
|
5844
|
+
this.mediaRecorderGeneration += 1;
|
|
5845
|
+
this.isSafeCaptureSettled = false;
|
|
5846
|
+
this.clearMediaRecorderErrorTimeout();
|
|
5847
|
+
this.activeRouteDecision = DEFAULT_ROUTE_DECISION;
|
|
5771
5848
|
}
|
|
5772
5849
|
resolveSafeCaptureMimeType() {
|
|
5773
5850
|
const mediaStream = this.streamManager.getStream();
|
|
5774
5851
|
const hasVideo = (mediaStream?.getVideoTracks().length ?? 0) > 0;
|
|
5775
5852
|
return hasVideo ? "video/webm" : "audio/webm";
|
|
5776
5853
|
}
|
|
5777
|
-
|
|
5854
|
+
resolveRecordedMimeType(recorder) {
|
|
5855
|
+
if (recorder.mimeType.trim().length > 0) {
|
|
5856
|
+
return recorder.mimeType;
|
|
5857
|
+
}
|
|
5858
|
+
const chunkWithMimeType = this.mediaRecorderChunks.find((chunk) => chunk.type.trim().length > 0);
|
|
5859
|
+
if (chunkWithMimeType !== undefined) {
|
|
5860
|
+
return chunkWithMimeType.type;
|
|
5861
|
+
}
|
|
5862
|
+
if (this.mediaRecorderRequestedMimeType !== null) {
|
|
5863
|
+
return this.mediaRecorderRequestedMimeType;
|
|
5864
|
+
}
|
|
5865
|
+
return this.resolveSafeCaptureMimeType();
|
|
5866
|
+
}
|
|
5867
|
+
resolveSafeCaptureMimeTypeCandidates() {
|
|
5778
5868
|
const canUseMimeType = (mimeType) => {
|
|
5779
5869
|
if (typeof MediaRecorder.isTypeSupported !== "function") {
|
|
5780
5870
|
return false;
|
|
@@ -5791,22 +5881,7 @@ class StreamRecordingState {
|
|
|
5791
5881
|
"video/mp4;codecs=avc1.42001f,mp4a.40.2",
|
|
5792
5882
|
"video/mp4"
|
|
5793
5883
|
];
|
|
5794
|
-
|
|
5795
|
-
if (this.shouldPreferWebmSafeCapture(config)) {
|
|
5796
|
-
candidateMimeTypes = [
|
|
5797
|
-
...webmCandidateMimeTypes,
|
|
5798
|
-
...mp4CandidateMimeTypes
|
|
5799
|
-
];
|
|
5800
|
-
} else if (config.format !== "mp4") {
|
|
5801
|
-
candidateMimeTypes = webmCandidateMimeTypes;
|
|
5802
|
-
}
|
|
5803
|
-
return candidateMimeTypes.find(canUseMimeType);
|
|
5804
|
-
}
|
|
5805
|
-
shouldPreferWebmSafeCapture(config) {
|
|
5806
|
-
if (config.format !== "mp4") {
|
|
5807
|
-
return false;
|
|
5808
|
-
}
|
|
5809
|
-
return this.activeRouteDecision.reasonCodes.includes(ANDROID_POST_RECORDING_TRANSCODE_REASON);
|
|
5884
|
+
return [...webmCandidateMimeTypes, ...mp4CandidateMimeTypes].filter(canUseMimeType);
|
|
5810
5885
|
}
|
|
5811
5886
|
scheduleMediaRecorderErrorFinalizeTimeout(recorder) {
|
|
5812
5887
|
if (this.mediaRecorderErrorTimeoutId !== null) {
|
|
@@ -5833,9 +5908,10 @@ class StreamRecordingState {
|
|
|
5833
5908
|
const error = new Error("Browser recording failed before upload could start");
|
|
5834
5909
|
error.name = "MediaRecorderRecordingError";
|
|
5835
5910
|
error.code = MEDIA_RECORDER_ERROR_CODE;
|
|
5836
|
-
error.mediaRecorderMimeType = recorder
|
|
5911
|
+
error.mediaRecorderMimeType = this.resolveRecordedMimeType(recorder);
|
|
5837
5912
|
error.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5838
5913
|
error.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5914
|
+
this.assignSafeCaptureErrorDiagnostics(error);
|
|
5839
5915
|
if (browserError?.name) {
|
|
5840
5916
|
error.mediaRecorderErrorName = browserError.name;
|
|
5841
5917
|
}
|
|
@@ -5848,9 +5924,10 @@ class StreamRecordingState {
|
|
|
5848
5924
|
const error = new Error("Browser recording produced no data before upload could start");
|
|
5849
5925
|
error.name = "MediaRecorderEmptyDataError";
|
|
5850
5926
|
error.code = MEDIA_RECORDER_EMPTY_ERROR_CODE;
|
|
5851
|
-
error.mediaRecorderMimeType = recorder
|
|
5927
|
+
error.mediaRecorderMimeType = this.resolveRecordedMimeType(recorder);
|
|
5852
5928
|
error.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5853
5929
|
error.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5930
|
+
this.assignSafeCaptureErrorDiagnostics(error);
|
|
5854
5931
|
if (this.mediaRecorderError?.mediaRecorderErrorName) {
|
|
5855
5932
|
error.mediaRecorderErrorName = this.mediaRecorderError.mediaRecorderErrorName;
|
|
5856
5933
|
}
|
|
@@ -5863,17 +5940,21 @@ class StreamRecordingState {
|
|
|
5863
5940
|
if (this.mediaRecorderError === null) {
|
|
5864
5941
|
return;
|
|
5865
5942
|
}
|
|
5866
|
-
this.mediaRecorderError.mediaRecorderMimeType = recorder
|
|
5943
|
+
this.mediaRecorderError.mediaRecorderMimeType = this.resolveRecordedMimeType(recorder);
|
|
5867
5944
|
this.mediaRecorderError.mediaRecorderChunkCount = this.mediaRecorderChunks.length;
|
|
5868
5945
|
this.mediaRecorderError.mediaRecorderChunkSize = this.getMediaRecorderChunkSize();
|
|
5946
|
+
this.assignSafeCaptureErrorDiagnostics(this.mediaRecorderError);
|
|
5869
5947
|
}
|
|
5870
5948
|
buildMediaRecorderDiagnostics(recorder) {
|
|
5871
5949
|
const diagnostics = {
|
|
5872
|
-
mediaRecorderMimeType: recorder
|
|
5950
|
+
mediaRecorderMimeType: this.resolveRecordedMimeType(recorder),
|
|
5873
5951
|
mediaRecorderChunkCount: this.mediaRecorderChunks.length,
|
|
5874
5952
|
mediaRecorderChunkSize: this.getMediaRecorderChunkSize(),
|
|
5875
5953
|
mediaRecorderHadError: this.mediaRecorderError !== null
|
|
5876
5954
|
};
|
|
5955
|
+
if (this.mediaRecorderRequestedMimeType !== null) {
|
|
5956
|
+
diagnostics.mediaRecorderRequestedMimeType = this.mediaRecorderRequestedMimeType;
|
|
5957
|
+
}
|
|
5877
5958
|
if (this.mediaRecorderError?.mediaRecorderErrorName) {
|
|
5878
5959
|
diagnostics.mediaRecorderErrorName = this.mediaRecorderError.mediaRecorderErrorName;
|
|
5879
5960
|
}
|
|
@@ -5882,6 +5963,15 @@ class StreamRecordingState {
|
|
|
5882
5963
|
}
|
|
5883
5964
|
return diagnostics;
|
|
5884
5965
|
}
|
|
5966
|
+
assignSafeCaptureErrorDiagnostics(error) {
|
|
5967
|
+
error.deferredIntegrityStage = "capture";
|
|
5968
|
+
if (this.mediaRecorderRequestedMimeType !== null) {
|
|
5969
|
+
error.mediaRecorderRequestedMimeType = this.mediaRecorderRequestedMimeType;
|
|
5970
|
+
}
|
|
5971
|
+
if (this.safeCaptureExpectedDurationSeconds !== null) {
|
|
5972
|
+
error.mediaRecorderExpectedDurationSeconds = this.safeCaptureExpectedDurationSeconds;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5885
5975
|
getMediaRecorderChunkSize() {
|
|
5886
5976
|
return this.mediaRecorderChunks.reduce((total, chunk) => total + chunk.size, 0);
|
|
5887
5977
|
}
|
|
@@ -5965,6 +6055,12 @@ class StreamRecordingState {
|
|
|
5965
6055
|
this.mediaRecorderStopResolve = null;
|
|
5966
6056
|
this.mediaRecorderStopReject = null;
|
|
5967
6057
|
this.mediaRecorderError = null;
|
|
6058
|
+
this.safeCaptureFrameRate = null;
|
|
6059
|
+
this.safeCaptureStartTime = null;
|
|
6060
|
+
this.safeCaptureExpectedDurationSeconds = null;
|
|
6061
|
+
this.mediaRecorderRequestedMimeType = null;
|
|
6062
|
+
this.mediaRecorderGeneration += 1;
|
|
6063
|
+
this.isSafeCaptureSettled = false;
|
|
5968
6064
|
this.clearMediaRecorderErrorTimeout();
|
|
5969
6065
|
this.activeRouteDecision = DEFAULT_ROUTE_DECISION;
|
|
5970
6066
|
if (this.streamProcessor) {
|
|
@@ -6112,6 +6208,7 @@ var RECORDING_CODEC_UNSUPPORTED_CODES = new Set([
|
|
|
6112
6208
|
"recording.video-codec-unavailable"
|
|
6113
6209
|
]);
|
|
6114
6210
|
var RECORDING_SERVER_TRANSCODE_FALLBACK_CODES = new Set([
|
|
6211
|
+
"recording.deferred-transcode-integrity",
|
|
6115
6212
|
"recording.post-recording-transcode-fallback",
|
|
6116
6213
|
"transcode.post-recording-fallback",
|
|
6117
6214
|
"route.post-recording-transcode-fallback"
|
|
@@ -6354,7 +6451,7 @@ function resolveDeviceError(input) {
|
|
|
6354
6451
|
// package.json
|
|
6355
6452
|
var package_default = {
|
|
6356
6453
|
name: "@vidtreo/recorder",
|
|
6357
|
-
version: "1.7.
|
|
6454
|
+
version: "1.7.12",
|
|
6358
6455
|
type: "module",
|
|
6359
6456
|
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.",
|
|
6360
6457
|
main: "./dist/index.js",
|
|
@@ -6439,12 +6536,23 @@ var MAX_RETRY_ATTEMPTS = 3;
|
|
|
6439
6536
|
var MAX_PENDING_EVENTS = 100;
|
|
6440
6537
|
var BRACKET_ERROR_CODE_PATTERN = /\[([a-z]+(?:[.-][a-z0-9]+)+)\]/i;
|
|
6441
6538
|
var INLINE_ERROR_CODE_PATTERN = /\b([a-z]+(?:[.-][a-z0-9]+)+)\b/i;
|
|
6442
|
-
var
|
|
6539
|
+
var RECORDING_INTEGRITY_ERROR_PROPERTY_KEYS = [
|
|
6443
6540
|
"mediaRecorderErrorName",
|
|
6444
6541
|
"mediaRecorderErrorMessage",
|
|
6445
6542
|
"mediaRecorderMimeType",
|
|
6446
6543
|
"mediaRecorderChunkCount",
|
|
6447
|
-
"mediaRecorderChunkSize"
|
|
6544
|
+
"mediaRecorderChunkSize",
|
|
6545
|
+
"mediaRecorderExpectedDurationSeconds",
|
|
6546
|
+
"mediaRecorderRequestedMimeType",
|
|
6547
|
+
"deferredIntegrityStage",
|
|
6548
|
+
"deferredSourceDurationSeconds",
|
|
6549
|
+
"deferredSourcePacketCount",
|
|
6550
|
+
"deferredSourcePacketRate",
|
|
6551
|
+
"deferredSourceMaximumPacketGap",
|
|
6552
|
+
"deferredOutputDurationSeconds",
|
|
6553
|
+
"deferredOutputPacketCount",
|
|
6554
|
+
"deferredOutputPacketRate",
|
|
6555
|
+
"deferredOutputMaximumPacketGap"
|
|
6448
6556
|
];
|
|
6449
6557
|
function resolveInstallationId(dependencies) {
|
|
6450
6558
|
const storageProvider = dependencies.storageProvider;
|
|
@@ -6800,7 +6908,7 @@ class TelemetryClient {
|
|
|
6800
6908
|
}
|
|
6801
6909
|
const candidate = error;
|
|
6802
6910
|
let properties = {};
|
|
6803
|
-
for (const key of
|
|
6911
|
+
for (const key of RECORDING_INTEGRITY_ERROR_PROPERTY_KEYS) {
|
|
6804
6912
|
const value = candidate[key];
|
|
6805
6913
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
6806
6914
|
properties = { ...properties, [key]: value };
|
|
@@ -8773,12 +8881,14 @@ var workerCode = `(() => {
|
|
|
8773
8881
|
}
|
|
8774
8882
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
8775
8883
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8884
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
8885
|
+
for (let key of __getOwnPropNames(mod))
|
|
8886
|
+
if (!__hasOwnProp.call(to, key))
|
|
8887
|
+
__defProp(to, key, {
|
|
8888
|
+
get: __accessProp.bind(mod, key),
|
|
8889
|
+
enumerable: true
|
|
8890
|
+
});
|
|
8891
|
+
}
|
|
8782
8892
|
if (canCache)
|
|
8783
8893
|
cache.set(mod, to);
|
|
8784
8894
|
return to;
|
|
@@ -8824,8 +8934,8 @@ var workerCode = `(() => {
|
|
|
8824
8934
|
// src/core/processor/worker/recorder-worker.ts
|
|
8825
8935
|
var exports_recorder_worker = {};
|
|
8826
8936
|
__export(exports_recorder_worker, {
|
|
8827
|
-
|
|
8828
|
-
|
|
8937
|
+
RecorderWorker: () => RecorderWorker,
|
|
8938
|
+
initializeRecorderWorker: () => initializeRecorderWorker
|
|
8829
8939
|
});
|
|
8830
8940
|
|
|
8831
8941
|
// ../../node_modules/.bun/mediabunny@1.45.2/node_modules/mediabunny/dist/modules/src/misc.js
|
|
@@ -22791,76 +22901,404 @@ class StreamProcessor {
|
|
|
22791
22901
|
}
|
|
22792
22902
|
|
|
22793
22903
|
// src/core/transcode/deferred-video-transcoder.ts
|
|
22794
|
-
import { ALL_FORMATS as
|
|
22904
|
+
import { ALL_FORMATS as ALL_FORMATS3, BlobSource as BlobSource4, EncodedPacketSink as EncodedPacketSink2, Input as Input4 } from "mediabunny";
|
|
22905
|
+
|
|
22906
|
+
// src/core/transcode/deferred-video-timing-repair.ts
|
|
22907
|
+
import {
|
|
22908
|
+
ALL_FORMATS as ALL_FORMATS2,
|
|
22909
|
+
BlobSource as BlobSource3,
|
|
22910
|
+
BufferTarget as BufferTarget2,
|
|
22911
|
+
EncodedAudioPacketSource,
|
|
22912
|
+
EncodedPacketSink,
|
|
22913
|
+
EncodedVideoPacketSource,
|
|
22914
|
+
Input as Input3,
|
|
22915
|
+
Mp4OutputFormat as Mp4OutputFormat2,
|
|
22916
|
+
Output as Output2
|
|
22917
|
+
} from "mediabunny";
|
|
22918
|
+
var MINIMUM_IMPLAUSIBLE_DURATION_SECONDS = 60;
|
|
22919
|
+
var EXPECTED_DURATION_MULTIPLIER = 2;
|
|
22920
|
+
var MAXIMUM_REPAIR_RATIO = 0.05;
|
|
22921
|
+
var MINIMUM_REPAIR_LIMIT = 2;
|
|
22922
|
+
var MAXIMUM_BOUNDARY_REGRESSION_MULTIPLIER = 2;
|
|
22923
|
+
var MINIMUM_BOUNDARY_REGRESSION_TOLERANCE_SECONDS = 0.25;
|
|
22924
|
+
function resolveImplausibleDurationThreshold(expectedDurationSeconds) {
|
|
22925
|
+
return Math.max(MINIMUM_IMPLAUSIBLE_DURATION_SECONDS, expectedDurationSeconds * EXPECTED_DURATION_MULTIPLIER);
|
|
22926
|
+
}
|
|
22927
|
+
function resolveMedian(values) {
|
|
22928
|
+
values.sort((left, right) => left - right);
|
|
22929
|
+
return values[Math.floor(values.length / 2)];
|
|
22930
|
+
}
|
|
22931
|
+
function validatePacketTimings(packets, expectedDurationSeconds) {
|
|
22932
|
+
if (!Number.isFinite(expectedDurationSeconds) || expectedDurationSeconds < 0) {
|
|
22933
|
+
throw new Error("Timing repair requires a valid recording duration");
|
|
22934
|
+
}
|
|
22935
|
+
if (packets.length === 0) {
|
|
22936
|
+
throw new Error("Timing repair requires video packets");
|
|
22937
|
+
}
|
|
22938
|
+
for (const packet of packets) {
|
|
22939
|
+
if (!Number.isFinite(packet.timestamp)) {
|
|
22940
|
+
throw new Error("Timing repair requires finite packet timestamps");
|
|
22941
|
+
}
|
|
22942
|
+
if (!Number.isFinite(packet.duration) || packet.duration < 0) {
|
|
22943
|
+
throw new Error("Timing repair requires valid packet durations");
|
|
22944
|
+
}
|
|
22945
|
+
}
|
|
22946
|
+
}
|
|
22947
|
+
function createDeferredVideoTimingRepairPlan(packets, expectedDurationSeconds) {
|
|
22948
|
+
validatePacketTimings(packets, expectedDurationSeconds);
|
|
22949
|
+
const threshold = resolveImplausibleDurationThreshold(expectedDurationSeconds);
|
|
22950
|
+
const packetTimings = packets.map((packet) => ({
|
|
22951
|
+
duration: packet.duration,
|
|
22952
|
+
isDurationRepaired: false,
|
|
22953
|
+
isTimestampRepaired: false,
|
|
22954
|
+
timestamp: packet.timestamp
|
|
22955
|
+
}));
|
|
22956
|
+
const repairIndexes = [];
|
|
22957
|
+
const validDurations = [];
|
|
22958
|
+
for (let index = 0;index < packets.length; index += 1) {
|
|
22959
|
+
const duration = packets[index].duration;
|
|
22960
|
+
if (duration > threshold) {
|
|
22961
|
+
repairIndexes.push(index);
|
|
22962
|
+
continue;
|
|
22963
|
+
}
|
|
22964
|
+
if (duration > 0) {
|
|
22965
|
+
validDurations.push(duration);
|
|
22966
|
+
}
|
|
22967
|
+
}
|
|
22968
|
+
if (repairIndexes.length === 0) {
|
|
22969
|
+
return {
|
|
22970
|
+
nominalDurationSeconds: null,
|
|
22971
|
+
packetTimings,
|
|
22972
|
+
repairedDurationCount: 0,
|
|
22973
|
+
repairedTimestampCount: 0
|
|
22974
|
+
};
|
|
22975
|
+
}
|
|
22976
|
+
const maximumRepairCount = Math.max(MINIMUM_REPAIR_LIMIT, Math.floor(packets.length * MAXIMUM_REPAIR_RATIO));
|
|
22977
|
+
if (repairIndexes.length > maximumRepairCount) {
|
|
22978
|
+
throw new Error("Video timing corruption is not isolated");
|
|
22979
|
+
}
|
|
22980
|
+
if (validDurations.length === 0) {
|
|
22981
|
+
throw new Error("Video timing recovery requires a valid source cadence");
|
|
22982
|
+
}
|
|
22983
|
+
for (let index = 1;index < repairIndexes.length; index += 1) {
|
|
22984
|
+
if (repairIndexes[index] - repairIndexes[index - 1] === 1) {
|
|
22985
|
+
throw new Error("Video timing corruption is not isolated");
|
|
22986
|
+
}
|
|
22987
|
+
}
|
|
22988
|
+
const nominalDurationSeconds = resolveMedian(validDurations);
|
|
22989
|
+
for (const index of repairIndexes) {
|
|
22990
|
+
packetTimings[index].duration = nominalDurationSeconds;
|
|
22991
|
+
packetTimings[index].isDurationRepaired = true;
|
|
22992
|
+
}
|
|
22993
|
+
let repairedTimestampCount = 0;
|
|
22994
|
+
const maximumBoundaryRegression = Math.max(MINIMUM_BOUNDARY_REGRESSION_TOLERANCE_SECONDS, nominalDurationSeconds * MAXIMUM_BOUNDARY_REGRESSION_MULTIPLIER);
|
|
22995
|
+
for (const index of repairIndexes) {
|
|
22996
|
+
const boundaryIndex = index + 1;
|
|
22997
|
+
if (boundaryIndex >= packetTimings.length) {
|
|
22998
|
+
continue;
|
|
22999
|
+
}
|
|
23000
|
+
const repairedPacket = packetTimings[index];
|
|
23001
|
+
const boundaryPacket = packetTimings[boundaryIndex];
|
|
23002
|
+
if (boundaryPacket.timestamp >= repairedPacket.timestamp) {
|
|
23003
|
+
continue;
|
|
23004
|
+
}
|
|
23005
|
+
const regression = repairedPacket.timestamp - boundaryPacket.timestamp;
|
|
23006
|
+
if (regression > maximumBoundaryRegression) {
|
|
23007
|
+
throw new Error("Video timing boundary regression is not recoverable");
|
|
23008
|
+
}
|
|
23009
|
+
boundaryPacket.timestamp = repairedPacket.timestamp + repairedPacket.duration;
|
|
23010
|
+
boundaryPacket.isTimestampRepaired = true;
|
|
23011
|
+
repairedTimestampCount += 1;
|
|
23012
|
+
}
|
|
23013
|
+
return {
|
|
23014
|
+
nominalDurationSeconds,
|
|
23015
|
+
packetTimings,
|
|
23016
|
+
repairedDurationCount: repairIndexes.length,
|
|
23017
|
+
repairedTimestampCount
|
|
23018
|
+
};
|
|
23019
|
+
}
|
|
23020
|
+
async function readVideoPacketTimings(videoTrack) {
|
|
23021
|
+
const packets = [];
|
|
23022
|
+
const sink = new EncodedPacketSink(videoTrack);
|
|
23023
|
+
for await (const packet of sink.packets(undefined, undefined, {
|
|
23024
|
+
metadataOnly: true
|
|
23025
|
+
})) {
|
|
23026
|
+
packets.push({
|
|
23027
|
+
duration: packet.duration,
|
|
23028
|
+
timestamp: packet.timestamp
|
|
23029
|
+
});
|
|
23030
|
+
}
|
|
23031
|
+
return packets;
|
|
23032
|
+
}
|
|
23033
|
+
async function addVideoTrack(output, videoTrack, videoSource) {
|
|
23034
|
+
const name = await videoTrack.getName();
|
|
23035
|
+
const metadata = {
|
|
23036
|
+
languageCode: await videoTrack.getLanguageCode(),
|
|
23037
|
+
rotation: await videoTrack.getRotation()
|
|
23038
|
+
};
|
|
23039
|
+
if (name !== null) {
|
|
23040
|
+
metadata.name = name;
|
|
23041
|
+
}
|
|
23042
|
+
output.addVideoTrack(videoSource, metadata);
|
|
23043
|
+
}
|
|
23044
|
+
async function addAudioTrack(output, audioTrack, audioSource) {
|
|
23045
|
+
const name = await audioTrack.getName();
|
|
23046
|
+
const metadata = {
|
|
23047
|
+
languageCode: await audioTrack.getLanguageCode()
|
|
23048
|
+
};
|
|
23049
|
+
if (name !== null) {
|
|
23050
|
+
metadata.name = name;
|
|
23051
|
+
}
|
|
23052
|
+
output.addAudioTrack(audioSource, metadata);
|
|
23053
|
+
}
|
|
23054
|
+
async function writeRepairedVideoPackets(videoTrack, videoSource, plan) {
|
|
23055
|
+
const decoderConfig = await videoTrack.getDecoderConfig();
|
|
23056
|
+
const metadata = {};
|
|
23057
|
+
if (decoderConfig !== null) {
|
|
23058
|
+
metadata.decoderConfig = decoderConfig;
|
|
23059
|
+
}
|
|
23060
|
+
let packetIndex = 0;
|
|
23061
|
+
const sink = new EncodedPacketSink(videoTrack);
|
|
23062
|
+
for await (const packet of sink.packets(undefined, undefined, {
|
|
23063
|
+
verifyKeyPackets: true
|
|
23064
|
+
})) {
|
|
23065
|
+
const timing = plan.packetTimings[packetIndex];
|
|
23066
|
+
if (timing === undefined) {
|
|
23067
|
+
throw new Error("Video packet count changed during timing repair");
|
|
23068
|
+
}
|
|
23069
|
+
const outputPacket = timing.isDurationRepaired || timing.isTimestampRepaired ? packet.clone({
|
|
23070
|
+
duration: timing.duration,
|
|
23071
|
+
timestamp: timing.timestamp
|
|
23072
|
+
}) : packet;
|
|
23073
|
+
await videoSource.add(outputPacket, metadata);
|
|
23074
|
+
packetIndex += 1;
|
|
23075
|
+
}
|
|
23076
|
+
if (packetIndex !== plan.packetTimings.length) {
|
|
23077
|
+
throw new Error("Video packet count changed during timing repair");
|
|
23078
|
+
}
|
|
23079
|
+
videoSource.close();
|
|
23080
|
+
}
|
|
23081
|
+
async function writeAudioPackets(audioTrack, audioSource) {
|
|
23082
|
+
const decoderConfig = await audioTrack.getDecoderConfig();
|
|
23083
|
+
const metadata = {};
|
|
23084
|
+
if (decoderConfig !== null) {
|
|
23085
|
+
metadata.decoderConfig = decoderConfig;
|
|
23086
|
+
}
|
|
23087
|
+
const sink = new EncodedPacketSink(audioTrack);
|
|
23088
|
+
for await (const packet of sink.packets()) {
|
|
23089
|
+
await audioSource.add(packet, metadata);
|
|
23090
|
+
}
|
|
23091
|
+
audioSource.close();
|
|
23092
|
+
}
|
|
23093
|
+
async function remuxRepairedVideo(input, videoTrack, plan) {
|
|
23094
|
+
const videoCodec = await videoTrack.getCodec();
|
|
23095
|
+
if (videoCodec === null) {
|
|
23096
|
+
throw new Error("Timing repair requires a supported video codec");
|
|
23097
|
+
}
|
|
23098
|
+
const audioTrack = await input.getPrimaryAudioTrack();
|
|
23099
|
+
let audioCodec = null;
|
|
23100
|
+
if (audioTrack !== null) {
|
|
23101
|
+
audioCodec = await audioTrack.getCodec();
|
|
23102
|
+
if (audioCodec === null) {
|
|
23103
|
+
throw new Error("Timing repair requires a supported audio codec");
|
|
23104
|
+
}
|
|
23105
|
+
}
|
|
23106
|
+
const target = new BufferTarget2;
|
|
23107
|
+
const output = new Output2({
|
|
23108
|
+
format: new Mp4OutputFormat2({ fastStart: "in-memory" }),
|
|
23109
|
+
target
|
|
23110
|
+
});
|
|
23111
|
+
const videoSource = new EncodedVideoPacketSource(videoCodec);
|
|
23112
|
+
await addVideoTrack(output, videoTrack, videoSource);
|
|
23113
|
+
let audioSource = null;
|
|
23114
|
+
if (audioTrack !== null && audioCodec !== null) {
|
|
23115
|
+
audioSource = new EncodedAudioPacketSource(audioCodec);
|
|
23116
|
+
await addAudioTrack(output, audioTrack, audioSource);
|
|
23117
|
+
}
|
|
23118
|
+
await output.start();
|
|
23119
|
+
const writers = [
|
|
23120
|
+
writeRepairedVideoPackets(videoTrack, videoSource, plan)
|
|
23121
|
+
];
|
|
23122
|
+
if (audioTrack !== null && audioSource !== null) {
|
|
23123
|
+
writers.push(writeAudioPackets(audioTrack, audioSource));
|
|
23124
|
+
}
|
|
23125
|
+
await Promise.all(writers);
|
|
23126
|
+
await output.finalize();
|
|
23127
|
+
if (target.buffer === null) {
|
|
23128
|
+
throw new Error("Timing repair did not produce an output buffer");
|
|
23129
|
+
}
|
|
23130
|
+
return new Blob([target.buffer], { type: "video/mp4" });
|
|
23131
|
+
}
|
|
23132
|
+
async function repairDeferredVideoTiming(blob, expectedDurationSeconds) {
|
|
23133
|
+
const input = new Input3({
|
|
23134
|
+
formats: ALL_FORMATS2,
|
|
23135
|
+
source: new BlobSource3(blob)
|
|
23136
|
+
});
|
|
23137
|
+
try {
|
|
23138
|
+
const videoTrack = await input.getPrimaryVideoTrack();
|
|
23139
|
+
if (videoTrack === null) {
|
|
23140
|
+
return {
|
|
23141
|
+
blob,
|
|
23142
|
+
diagnostics: {
|
|
23143
|
+
deferredTimingRepairedDurationCount: 0,
|
|
23144
|
+
deferredTimingRepairedTimestampCount: 0
|
|
23145
|
+
}
|
|
23146
|
+
};
|
|
23147
|
+
}
|
|
23148
|
+
const packetTimings = await readVideoPacketTimings(videoTrack);
|
|
23149
|
+
const plan = createDeferredVideoTimingRepairPlan(packetTimings, expectedDurationSeconds);
|
|
23150
|
+
const diagnostics = {
|
|
23151
|
+
deferredTimingRepairedDurationCount: plan.repairedDurationCount,
|
|
23152
|
+
deferredTimingRepairedTimestampCount: plan.repairedTimestampCount
|
|
23153
|
+
};
|
|
23154
|
+
if (plan.nominalDurationSeconds !== null) {
|
|
23155
|
+
diagnostics.deferredTimingNominalDurationSeconds = plan.nominalDurationSeconds;
|
|
23156
|
+
}
|
|
23157
|
+
if (plan.repairedDurationCount === 0) {
|
|
23158
|
+
return { blob, diagnostics };
|
|
23159
|
+
}
|
|
23160
|
+
return {
|
|
23161
|
+
blob: await remuxRepairedVideo(input, videoTrack, plan),
|
|
23162
|
+
diagnostics
|
|
23163
|
+
};
|
|
23164
|
+
} finally {
|
|
23165
|
+
input.dispose();
|
|
23166
|
+
}
|
|
23167
|
+
}
|
|
23168
|
+
|
|
23169
|
+
// src/core/transcode/deferred-video-transcoder.ts
|
|
22795
23170
|
var MINIMUM_FRAME_DENSITY_RATIO = 0.9;
|
|
22796
23171
|
var MAXIMUM_DURATION_DIFFERENCE_RATIO = 0.05;
|
|
22797
23172
|
var MINIMUM_DURATION_TOLERANCE_SECONDS = 0.25;
|
|
22798
23173
|
var MAXIMUM_FRAME_GAP_MULTIPLIER = 5;
|
|
22799
23174
|
var MINIMUM_FRAME_GAP_TOLERANCE_SECONDS = 0.25;
|
|
23175
|
+
var LONG_RECORDING_THRESHOLD_SECONDS = 3;
|
|
23176
|
+
var LONG_RECORDING_DURATION_TOLERANCE_SECONDS = 1.5;
|
|
23177
|
+
var LONG_RECORDING_DURATION_TOLERANCE_RATIO = 0.15;
|
|
23178
|
+
var SHORT_RECORDING_DURATION_TOLERANCE_SECONDS = 0.75;
|
|
23179
|
+
var SHORT_RECORDING_DURATION_TOLERANCE_RATIO = 0.5;
|
|
22800
23180
|
var INTEGRITY_ERROR_CODE = "recording.deferred-transcode-integrity";
|
|
22801
|
-
function createIntegrityError(message) {
|
|
23181
|
+
function createIntegrityError(message, diagnostics = {}) {
|
|
22802
23182
|
const error = new Error(message);
|
|
22803
23183
|
error.name = "DeferredTranscodeIntegrityError";
|
|
22804
23184
|
error.code = INTEGRITY_ERROR_CODE;
|
|
22805
23185
|
error.isRecoverable = true;
|
|
23186
|
+
Object.assign(error, diagnostics);
|
|
22806
23187
|
return error;
|
|
22807
23188
|
}
|
|
22808
|
-
function validatePacketSummary(summary, label) {
|
|
23189
|
+
function validatePacketSummary(summary, label, diagnostics) {
|
|
22809
23190
|
if (!Number.isFinite(summary.averagePacketRate)) {
|
|
22810
|
-
throw createIntegrityError(`${label} video packet rate is invalid
|
|
23191
|
+
throw createIntegrityError(`${label} video packet rate is invalid`, diagnostics);
|
|
22811
23192
|
}
|
|
22812
23193
|
if (!Number.isFinite(summary.duration)) {
|
|
22813
|
-
throw createIntegrityError(`${label} video duration is invalid
|
|
23194
|
+
throw createIntegrityError(`${label} video duration is invalid`, diagnostics);
|
|
22814
23195
|
}
|
|
22815
23196
|
if (!Number.isFinite(summary.maximumPacketGap)) {
|
|
22816
|
-
throw createIntegrityError(`${label} video maximum packet gap is invalid
|
|
23197
|
+
throw createIntegrityError(`${label} video maximum packet gap is invalid`, diagnostics);
|
|
22817
23198
|
}
|
|
22818
23199
|
if (summary.averagePacketRate <= 0 || summary.duration <= 0) {
|
|
22819
|
-
throw createIntegrityError(`${label} video packet statistics are empty
|
|
23200
|
+
throw createIntegrityError(`${label} video packet statistics are empty`, diagnostics);
|
|
22820
23201
|
}
|
|
22821
23202
|
if (summary.maximumPacketGap < 0) {
|
|
22822
|
-
throw createIntegrityError(`${label} video maximum packet gap is invalid
|
|
23203
|
+
throw createIntegrityError(`${label} video maximum packet gap is invalid`, diagnostics);
|
|
22823
23204
|
}
|
|
22824
23205
|
if (!Number.isInteger(summary.packetCount) || summary.packetCount <= 0) {
|
|
22825
|
-
throw createIntegrityError(`${label} video packet count is invalid
|
|
23206
|
+
throw createIntegrityError(`${label} video packet count is invalid`, diagnostics);
|
|
23207
|
+
}
|
|
23208
|
+
}
|
|
23209
|
+
function validateMaximumFrameGap(summary, label, targetFrameRate, diagnostics) {
|
|
23210
|
+
const maximumPacketGap = Math.max(MINIMUM_FRAME_GAP_TOLERANCE_SECONDS, MAXIMUM_FRAME_GAP_MULTIPLIER / targetFrameRate);
|
|
23211
|
+
if (summary.maximumPacketGap > maximumPacketGap) {
|
|
23212
|
+
throw createIntegrityError(`${label} video contains a material frame gap`, diagnostics);
|
|
22826
23213
|
}
|
|
22827
23214
|
}
|
|
22828
|
-
function
|
|
23215
|
+
function validateOutputFrameDensity(summary, targetFrameRate, diagnostics) {
|
|
22829
23216
|
const minimumPacketRate = targetFrameRate * MINIMUM_FRAME_DENSITY_RATIO;
|
|
22830
23217
|
if (summary.averagePacketRate < minimumPacketRate) {
|
|
22831
|
-
throw createIntegrityError(
|
|
23218
|
+
throw createIntegrityError("Output video frame density is below the capture target", diagnostics);
|
|
22832
23219
|
}
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
23220
|
+
}
|
|
23221
|
+
function buildIntegrityDiagnostics(validation, additionalDiagnostics = {}) {
|
|
23222
|
+
let stage = "output";
|
|
23223
|
+
if (validation.stage !== undefined) {
|
|
23224
|
+
stage = validation.stage;
|
|
23225
|
+
}
|
|
23226
|
+
const diagnostics = {
|
|
23227
|
+
...additionalDiagnostics,
|
|
23228
|
+
deferredIntegrityStage: stage
|
|
23229
|
+
};
|
|
23230
|
+
assignFiniteDiagnostic(diagnostics, "mediaRecorderExpectedDurationSeconds", validation.expectedDurationSeconds);
|
|
23231
|
+
if (validation.source !== null) {
|
|
23232
|
+
assignFiniteDiagnostic(diagnostics, "deferredSourceDurationSeconds", validation.source.duration);
|
|
23233
|
+
assignFiniteDiagnostic(diagnostics, "deferredSourcePacketCount", validation.source.packetCount);
|
|
23234
|
+
assignFiniteDiagnostic(diagnostics, "deferredSourcePacketRate", validation.source.averagePacketRate);
|
|
23235
|
+
assignFiniteDiagnostic(diagnostics, "deferredSourceMaximumPacketGap", validation.source.maximumPacketGap);
|
|
23236
|
+
}
|
|
23237
|
+
if (validation.output !== null) {
|
|
23238
|
+
assignFiniteDiagnostic(diagnostics, "deferredOutputDurationSeconds", validation.output.duration);
|
|
23239
|
+
assignFiniteDiagnostic(diagnostics, "deferredOutputPacketCount", validation.output.packetCount);
|
|
23240
|
+
assignFiniteDiagnostic(diagnostics, "deferredOutputPacketRate", validation.output.averagePacketRate);
|
|
23241
|
+
assignFiniteDiagnostic(diagnostics, "deferredOutputMaximumPacketGap", validation.output.maximumPacketGap);
|
|
22836
23242
|
}
|
|
23243
|
+
return diagnostics;
|
|
22837
23244
|
}
|
|
22838
|
-
function
|
|
23245
|
+
function assignFiniteDiagnostic(diagnostics, key, value) {
|
|
23246
|
+
if (Number.isFinite(value)) {
|
|
23247
|
+
diagnostics[key] = value;
|
|
23248
|
+
}
|
|
23249
|
+
}
|
|
23250
|
+
function resolveLifecycleDurationTolerance(expectedDurationSeconds) {
|
|
23251
|
+
if (expectedDurationSeconds >= LONG_RECORDING_THRESHOLD_SECONDS) {
|
|
23252
|
+
return Math.max(LONG_RECORDING_DURATION_TOLERANCE_SECONDS, expectedDurationSeconds * LONG_RECORDING_DURATION_TOLERANCE_RATIO);
|
|
23253
|
+
}
|
|
23254
|
+
return Math.max(SHORT_RECORDING_DURATION_TOLERANCE_SECONDS, expectedDurationSeconds * SHORT_RECORDING_DURATION_TOLERANCE_RATIO);
|
|
23255
|
+
}
|
|
23256
|
+
function validateLifecycleCoverage(summary, label, expectedDurationSeconds, diagnostics) {
|
|
23257
|
+
const durationDifference = Math.abs(summary.duration - expectedDurationSeconds);
|
|
23258
|
+
const tolerance = resolveLifecycleDurationTolerance(expectedDurationSeconds);
|
|
23259
|
+
if (durationDifference > tolerance) {
|
|
23260
|
+
throw createIntegrityError(`${label} video duration does not cover the active recording`, diagnostics);
|
|
23261
|
+
}
|
|
23262
|
+
}
|
|
23263
|
+
function validateDeferredVideoStatsWithDiagnostics(validation, additionalDiagnostics) {
|
|
23264
|
+
const diagnostics = buildIntegrityDiagnostics(validation, additionalDiagnostics);
|
|
23265
|
+
if (!Number.isFinite(validation.expectedDurationSeconds) || validation.expectedDurationSeconds < 0) {
|
|
23266
|
+
throw createIntegrityError("Deferred transcode requires a valid active recording duration", diagnostics);
|
|
23267
|
+
}
|
|
22839
23268
|
if (!Number.isFinite(validation.targetFrameRate) || validation.targetFrameRate <= 0) {
|
|
22840
|
-
throw createIntegrityError("Deferred transcode requires a valid capture frame rate");
|
|
23269
|
+
throw createIntegrityError("Deferred transcode requires a valid capture frame rate", diagnostics);
|
|
22841
23270
|
}
|
|
22842
|
-
if (
|
|
22843
|
-
throw createIntegrityError("Deferred transcode source
|
|
23271
|
+
if (validation.source === null) {
|
|
23272
|
+
throw createIntegrityError("Deferred transcode source has no primary video track", diagnostics);
|
|
22844
23273
|
}
|
|
22845
|
-
validatePacketSummary(validation.source, "Source");
|
|
22846
|
-
|
|
22847
|
-
|
|
22848
|
-
|
|
23274
|
+
validatePacketSummary(validation.source, "Source", diagnostics);
|
|
23275
|
+
validateMaximumFrameGap(validation.source, "Source", validation.targetFrameRate, diagnostics);
|
|
23276
|
+
validateLifecycleCoverage(validation.source, "Source", validation.expectedDurationSeconds, diagnostics);
|
|
23277
|
+
if (validation.stage === "source") {
|
|
23278
|
+
return;
|
|
23279
|
+
}
|
|
23280
|
+
if (validation.output === null) {
|
|
23281
|
+
throw createIntegrityError("Deferred transcode output has no primary video track", diagnostics);
|
|
23282
|
+
}
|
|
23283
|
+
validatePacketSummary(validation.output, "Output", diagnostics);
|
|
23284
|
+
validateOutputFrameDensity(validation.output, validation.targetFrameRate, diagnostics);
|
|
23285
|
+
validateMaximumFrameGap(validation.output, "Output", validation.targetFrameRate, diagnostics);
|
|
23286
|
+
validateLifecycleCoverage(validation.output, "Output", validation.expectedDurationSeconds, diagnostics);
|
|
22849
23287
|
const durationDifference = Math.abs(validation.source.duration - validation.output.duration);
|
|
22850
23288
|
const durationTolerance = Math.max(MINIMUM_DURATION_TOLERANCE_SECONDS, validation.source.duration * MAXIMUM_DURATION_DIFFERENCE_RATIO);
|
|
22851
23289
|
if (durationDifference > durationTolerance) {
|
|
22852
|
-
throw createIntegrityError("Deferred transcode output duration differs materially from source");
|
|
23290
|
+
throw createIntegrityError("Deferred transcode output duration differs materially from source", diagnostics);
|
|
22853
23291
|
}
|
|
22854
23292
|
const minimumPacketRate = validation.targetFrameRate * MINIMUM_FRAME_DENSITY_RATIO;
|
|
22855
23293
|
const minimumPacketCount = Math.floor(validation.source.duration * minimumPacketRate);
|
|
22856
23294
|
if (validation.output.averagePacketRate < minimumPacketRate || validation.output.packetCount < minimumPacketCount) {
|
|
22857
|
-
throw createIntegrityError("Deferred transcode output lost material video frame density");
|
|
23295
|
+
throw createIntegrityError("Deferred transcode output lost material video frame density", diagnostics);
|
|
22858
23296
|
}
|
|
22859
23297
|
}
|
|
22860
23298
|
async function inspectVideoPackets(blob) {
|
|
22861
|
-
const input = new
|
|
22862
|
-
formats:
|
|
22863
|
-
source: new
|
|
23299
|
+
const input = new Input4({
|
|
23300
|
+
formats: ALL_FORMATS3,
|
|
23301
|
+
source: new BlobSource4(blob)
|
|
22864
23302
|
});
|
|
22865
23303
|
const videoTrack = await input.getPrimaryVideoTrack();
|
|
22866
23304
|
if (videoTrack === null) {
|
|
@@ -22868,7 +23306,7 @@ async function inspectVideoPackets(blob) {
|
|
|
22868
23306
|
return null;
|
|
22869
23307
|
}
|
|
22870
23308
|
try {
|
|
22871
|
-
const packetSink = new
|
|
23309
|
+
const packetSink = new EncodedPacketSink2(videoTrack);
|
|
22872
23310
|
const timestamps = [];
|
|
22873
23311
|
let firstTimestamp = Number.POSITIVE_INFINITY;
|
|
22874
23312
|
let lastEndTimestamp = Number.NEGATIVE_INFINITY;
|
|
@@ -22897,27 +23335,86 @@ async function inspectVideoPackets(blob) {
|
|
|
22897
23335
|
input.dispose();
|
|
22898
23336
|
}
|
|
22899
23337
|
}
|
|
22900
|
-
async function inspectDeferredVideo(blob, label) {
|
|
23338
|
+
async function inspectDeferredVideo(blob, label, diagnostics) {
|
|
22901
23339
|
return await inspectVideoPackets(blob).catch((error) => {
|
|
22902
23340
|
const message = extractErrorMessage(error);
|
|
22903
|
-
throw createIntegrityError(`Could not inspect ${label} deferred video: ${message}
|
|
23341
|
+
throw createIntegrityError(`Could not inspect ${label} deferred video: ${message}`, diagnostics);
|
|
23342
|
+
});
|
|
23343
|
+
}
|
|
23344
|
+
async function repairDeferredVideoSource(input, expectedDurationSeconds) {
|
|
23345
|
+
return await repairDeferredVideoTiming(input, expectedDurationSeconds).catch((error) => {
|
|
23346
|
+
const message = extractErrorMessage(error);
|
|
23347
|
+
throw createIntegrityError(`Could not repair deferred video timing: ${message}`, {
|
|
23348
|
+
deferredIntegrityStage: "source-repair",
|
|
23349
|
+
mediaRecorderExpectedDurationSeconds: expectedDurationSeconds
|
|
23350
|
+
});
|
|
22904
23351
|
});
|
|
22905
23352
|
}
|
|
22906
|
-
async function transcodeDeferredVideo(input, config, onProgress) {
|
|
23353
|
+
async function transcodeDeferredVideo(input, config, onProgress, context) {
|
|
22907
23354
|
const targetFrameRate = config.fps;
|
|
22908
23355
|
if (typeof targetFrameRate !== "number" || !Number.isFinite(targetFrameRate) || targetFrameRate <= 0) {
|
|
22909
|
-
throw createIntegrityError("Deferred transcode requires a valid capture frame rate"
|
|
23356
|
+
throw createIntegrityError("Deferred transcode requires a valid capture frame rate", {
|
|
23357
|
+
deferredIntegrityStage: "source",
|
|
23358
|
+
mediaRecorderExpectedDurationSeconds: context.expectedDurationSeconds
|
|
23359
|
+
});
|
|
22910
23360
|
}
|
|
22911
|
-
const
|
|
22912
|
-
|
|
22913
|
-
|
|
23361
|
+
const timingRepair = await repairDeferredVideoSource(input, context.expectedDurationSeconds);
|
|
23362
|
+
const source = await inspectDeferredVideo(timingRepair.blob, "source", {
|
|
23363
|
+
deferredIntegrityStage: "source",
|
|
23364
|
+
mediaRecorderExpectedDurationSeconds: context.expectedDurationSeconds,
|
|
23365
|
+
...timingRepair.diagnostics
|
|
23366
|
+
});
|
|
23367
|
+
validateDeferredVideoStatsWithDiagnostics({
|
|
23368
|
+
expectedDurationSeconds: context.expectedDurationSeconds,
|
|
23369
|
+
output: null,
|
|
22914
23370
|
source,
|
|
23371
|
+
stage: "source",
|
|
23372
|
+
targetFrameRate
|
|
23373
|
+
}, timingRepair.diagnostics);
|
|
23374
|
+
const transcodeDiagnostics = buildIntegrityDiagnostics({
|
|
23375
|
+
expectedDurationSeconds: context.expectedDurationSeconds,
|
|
23376
|
+
output: null,
|
|
23377
|
+
source,
|
|
23378
|
+
stage: "source",
|
|
22915
23379
|
targetFrameRate
|
|
22916
23380
|
});
|
|
22917
|
-
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
23381
|
+
transcodeDiagnostics.deferredIntegrityStage = "transcode";
|
|
23382
|
+
Object.assign(transcodeDiagnostics, timingRepair.diagnostics);
|
|
23383
|
+
const result = await transcodeVideo(timingRepair.blob, config, onProgress).catch((error) => {
|
|
23384
|
+
let transcodeError;
|
|
23385
|
+
if (error instanceof Error) {
|
|
23386
|
+
transcodeError = error;
|
|
23387
|
+
} else {
|
|
23388
|
+
transcodeError = new Error(extractErrorMessage(error));
|
|
23389
|
+
}
|
|
23390
|
+
Object.assign(transcodeError, transcodeDiagnostics);
|
|
23391
|
+
throw transcodeError;
|
|
23392
|
+
});
|
|
23393
|
+
const outputDiagnostics = {
|
|
23394
|
+
deferredIntegrityStage: "output",
|
|
23395
|
+
mediaRecorderExpectedDurationSeconds: context.expectedDurationSeconds,
|
|
23396
|
+
...timingRepair.diagnostics
|
|
23397
|
+
};
|
|
23398
|
+
if (source !== null) {
|
|
23399
|
+
outputDiagnostics.deferredSourceDurationSeconds = source.duration;
|
|
23400
|
+
outputDiagnostics.deferredSourcePacketCount = source.packetCount;
|
|
23401
|
+
outputDiagnostics.deferredSourcePacketRate = source.averagePacketRate;
|
|
23402
|
+
outputDiagnostics.deferredSourceMaximumPacketGap = source.maximumPacketGap;
|
|
23403
|
+
}
|
|
23404
|
+
const output = await inspectDeferredVideo(result.blob, "output", outputDiagnostics);
|
|
23405
|
+
const validation = {
|
|
23406
|
+
expectedDurationSeconds: context.expectedDurationSeconds,
|
|
23407
|
+
output,
|
|
23408
|
+
source,
|
|
23409
|
+
stage: "output",
|
|
23410
|
+
targetFrameRate
|
|
23411
|
+
};
|
|
23412
|
+
validateDeferredVideoStatsWithDiagnostics(validation, timingRepair.diagnostics);
|
|
23413
|
+
const diagnostics = buildIntegrityDiagnostics(validation, timingRepair.diagnostics);
|
|
23414
|
+
return {
|
|
23415
|
+
...result,
|
|
23416
|
+
diagnostics
|
|
23417
|
+
};
|
|
22921
23418
|
}
|
|
22922
23419
|
|
|
22923
23420
|
// src/core/recording/recording-manager.ts
|
|
@@ -23171,7 +23668,8 @@ class RecordingManager {
|
|
|
23171
23668
|
this.callbacks.onStopAudioTracking();
|
|
23172
23669
|
logger.debug("[RecordingManager] Stopping recording in stream manager");
|
|
23173
23670
|
const stopResult = await this.streamManager.stopRecording();
|
|
23174
|
-
const
|
|
23671
|
+
const finalResult = await this.resolveFinalRecordingBlob(stopResult.blob, stopResult.captureFrameRate, stopResult.expectedDurationSeconds);
|
|
23672
|
+
const finalBlob = finalResult.blob;
|
|
23175
23673
|
logger.info("[RecordingManager] Recording stopped, blob size:", finalBlob.size);
|
|
23176
23674
|
this.recordingState = RECORDING_STATE_IDLE;
|
|
23177
23675
|
this.callbacks.onStateChange(this.recordingState);
|
|
@@ -23193,6 +23691,9 @@ class RecordingManager {
|
|
|
23193
23691
|
if (stopResult.mediaRecorderDiagnostics !== undefined) {
|
|
23194
23692
|
Object.assign(telemetryProperties, stopResult.mediaRecorderDiagnostics);
|
|
23195
23693
|
}
|
|
23694
|
+
if (finalResult.diagnostics !== undefined) {
|
|
23695
|
+
Object.assign(telemetryProperties, finalResult.diagnostics);
|
|
23696
|
+
}
|
|
23196
23697
|
return {
|
|
23197
23698
|
blob: finalBlob,
|
|
23198
23699
|
telemetryProperties
|
|
@@ -23273,13 +23774,16 @@ class RecordingManager {
|
|
|
23273
23774
|
this.recordingStartTime = null;
|
|
23274
23775
|
this.maxTimeRemaining = null;
|
|
23275
23776
|
}
|
|
23276
|
-
async resolveFinalRecordingBlob(blob, captureFrameRate) {
|
|
23777
|
+
async resolveFinalRecordingBlob(blob, captureFrameRate, expectedDurationSeconds) {
|
|
23277
23778
|
if (this.activeRouteDecision.route !== "safe-capture-post-recording-transcode") {
|
|
23278
|
-
return blob;
|
|
23779
|
+
return { blob };
|
|
23279
23780
|
}
|
|
23280
23781
|
if (!this.activeRecordingConfig) {
|
|
23281
23782
|
throw new Error("Recording config unavailable for final video processing");
|
|
23282
23783
|
}
|
|
23784
|
+
if (typeof expectedDurationSeconds !== "number" || !Number.isFinite(expectedDurationSeconds) || expectedDurationSeconds < 0) {
|
|
23785
|
+
throw new Error("Recording active duration unavailable for final video processing");
|
|
23786
|
+
}
|
|
23283
23787
|
const transcodeConfig = {
|
|
23284
23788
|
...this.activeRecordingConfig,
|
|
23285
23789
|
fps: this.resolveDeferredCaptureFrameRate(captureFrameRate),
|
|
@@ -23288,9 +23792,12 @@ class RecordingManager {
|
|
|
23288
23792
|
this.callbacks.onTranscodingProgress?.(0);
|
|
23289
23793
|
const result = await this.dependencies.transcodeVideo(blob, transcodeConfig, (progress) => {
|
|
23290
23794
|
this.callbacks.onTranscodingProgress?.(progress);
|
|
23291
|
-
});
|
|
23795
|
+
}, { expectedDurationSeconds });
|
|
23292
23796
|
this.callbacks.onTranscodingProgress?.(1);
|
|
23293
|
-
return
|
|
23797
|
+
return {
|
|
23798
|
+
blob: result.blob,
|
|
23799
|
+
diagnostics: result.diagnostics
|
|
23800
|
+
};
|
|
23294
23801
|
}
|
|
23295
23802
|
resolveDeferredCaptureFrameRate(captureFrameRate) {
|
|
23296
23803
|
if (typeof captureFrameRate === "number" && Number.isFinite(captureFrameRate) && captureFrameRate > 0) {
|
|
@@ -24743,79 +25250,79 @@ class VidtreoRecorder {
|
|
|
24743
25250
|
}
|
|
24744
25251
|
}
|
|
24745
25252
|
export {
|
|
24746
|
-
|
|
24747
|
-
validateBrowserSupport,
|
|
24748
|
-
transcodeVideoForNativeCamera,
|
|
24749
|
-
transcodeVideo,
|
|
24750
|
-
resolveDeviceError,
|
|
24751
|
-
requireStream,
|
|
24752
|
-
requireProcessor,
|
|
24753
|
-
requireNonNull,
|
|
24754
|
-
requireMediaDevices,
|
|
24755
|
-
requireInitialized,
|
|
24756
|
-
requireDefined,
|
|
24757
|
-
requireActive,
|
|
24758
|
-
recoverPendingUpload,
|
|
24759
|
-
parseBrowserErrorLinkContent,
|
|
24760
|
-
mapPresetToConfig,
|
|
24761
|
-
logger,
|
|
24762
|
-
isMobileDevice2 as isMobileDevice,
|
|
24763
|
-
isAudioRuntimeMissingCode,
|
|
24764
|
-
isAudioMissingError,
|
|
24765
|
-
getPresetVideoBitrateForFormat,
|
|
24766
|
-
getPresetTotalBitrate,
|
|
24767
|
-
getPresetAudioBitrateForFormat,
|
|
24768
|
-
getFormatCompatibilityPolicy,
|
|
24769
|
-
getDefaultConfigForFormat,
|
|
24770
|
-
getDefaultAudioCodecForFormat,
|
|
24771
|
-
getCameraErrorText,
|
|
24772
|
-
getBrowserName,
|
|
24773
|
-
getBrowserInfo,
|
|
24774
|
-
getBrowserErrorText,
|
|
24775
|
-
getAudioErrorText,
|
|
24776
|
-
getAudioCodecForFormat,
|
|
24777
|
-
formatTime,
|
|
24778
|
-
formatFileSize,
|
|
24779
|
-
extractVideoDuration,
|
|
24780
|
-
extractLastFrame,
|
|
24781
|
-
extractErrorMessage,
|
|
24782
|
-
evaluateAudioHealth,
|
|
24783
|
-
createPermissionFlowOrchestratorDependencies,
|
|
24784
|
-
createAudioNoTrackAtStartError,
|
|
24785
|
-
createAudioMissingError,
|
|
24786
|
-
checkRecorderSupport,
|
|
24787
|
-
calculateVideoBitrate,
|
|
24788
|
-
calculateTotalBitrateFromMbPerMinute,
|
|
24789
|
-
calculateBarColor,
|
|
24790
|
-
VidtreoRecorder,
|
|
24791
|
-
VideoUploadService,
|
|
24792
|
-
VideoStorageService,
|
|
24793
|
-
UploadQueueManager,
|
|
24794
|
-
StreamProcessor,
|
|
24795
|
-
StorageManager,
|
|
24796
|
-
RecordingManager,
|
|
24797
|
-
RecorderController,
|
|
24798
|
-
QuotaManager,
|
|
24799
|
-
PermissionFlowOrchestrator,
|
|
24800
|
-
PRESET_SIZE_LIMIT_MB_PER_MINUTE,
|
|
24801
|
-
NativeCameraHandler,
|
|
24802
|
-
FORMAT_DEFAULT_CODECS,
|
|
24803
|
-
FORMAT_COMPATIBILITY_POLICY,
|
|
24804
|
-
ERROR_RECORDING_NO_AUDIO_TRACK,
|
|
24805
|
-
ERROR_RECORDING_NO_AUDIO_FRAMES,
|
|
24806
|
-
ERROR_RECORDING_AUDIO_ZERO_CHANNELS,
|
|
24807
|
-
ERROR_RECORDING_AUDIO_TRACK_ENDED,
|
|
24808
|
-
ERROR_RECORDING_AUDIO_SILENT,
|
|
24809
|
-
ERROR_RECORDING_AUDIO_NO_CHUNKS,
|
|
24810
|
-
ERROR_RECORDING_AUDIO_INVALID_SAMPLE_RATE,
|
|
24811
|
-
ERROR_AUDIO_NO_TRACK_AT_START,
|
|
24812
|
-
DeviceManager,
|
|
24813
|
-
DEFAULT_TRANSCODE_CONFIG,
|
|
24814
|
-
DEFAULT_STREAM_CONFIG,
|
|
24815
|
-
DEFAULT_RECORDING_OPTIONS,
|
|
24816
|
-
DEFAULT_CAMERA_CONSTRAINTS,
|
|
24817
|
-
ConfigService,
|
|
24818
|
-
ConfigManager,
|
|
25253
|
+
AudioLevelAnalyzer,
|
|
24819
25254
|
CameraStreamManager,
|
|
24820
|
-
|
|
25255
|
+
ConfigManager,
|
|
25256
|
+
ConfigService,
|
|
25257
|
+
DEFAULT_CAMERA_CONSTRAINTS,
|
|
25258
|
+
DEFAULT_RECORDING_OPTIONS,
|
|
25259
|
+
DEFAULT_STREAM_CONFIG,
|
|
25260
|
+
DEFAULT_TRANSCODE_CONFIG,
|
|
25261
|
+
DeviceManager,
|
|
25262
|
+
ERROR_AUDIO_NO_TRACK_AT_START,
|
|
25263
|
+
ERROR_RECORDING_AUDIO_INVALID_SAMPLE_RATE,
|
|
25264
|
+
ERROR_RECORDING_AUDIO_NO_CHUNKS,
|
|
25265
|
+
ERROR_RECORDING_AUDIO_SILENT,
|
|
25266
|
+
ERROR_RECORDING_AUDIO_TRACK_ENDED,
|
|
25267
|
+
ERROR_RECORDING_AUDIO_ZERO_CHANNELS,
|
|
25268
|
+
ERROR_RECORDING_NO_AUDIO_FRAMES,
|
|
25269
|
+
ERROR_RECORDING_NO_AUDIO_TRACK,
|
|
25270
|
+
FORMAT_COMPATIBILITY_POLICY,
|
|
25271
|
+
FORMAT_DEFAULT_CODECS,
|
|
25272
|
+
NativeCameraHandler,
|
|
25273
|
+
PRESET_SIZE_LIMIT_MB_PER_MINUTE,
|
|
25274
|
+
PermissionFlowOrchestrator,
|
|
25275
|
+
QuotaManager,
|
|
25276
|
+
RecorderController,
|
|
25277
|
+
RecordingManager,
|
|
25278
|
+
StorageManager,
|
|
25279
|
+
StreamProcessor,
|
|
25280
|
+
UploadQueueManager,
|
|
25281
|
+
VideoStorageService,
|
|
25282
|
+
VideoUploadService,
|
|
25283
|
+
VidtreoRecorder,
|
|
25284
|
+
calculateBarColor,
|
|
25285
|
+
calculateTotalBitrateFromMbPerMinute,
|
|
25286
|
+
calculateVideoBitrate,
|
|
25287
|
+
checkRecorderSupport,
|
|
25288
|
+
createAudioMissingError,
|
|
25289
|
+
createAudioNoTrackAtStartError,
|
|
25290
|
+
createPermissionFlowOrchestratorDependencies,
|
|
25291
|
+
evaluateAudioHealth,
|
|
25292
|
+
extractErrorMessage,
|
|
25293
|
+
extractLastFrame,
|
|
25294
|
+
extractVideoDuration,
|
|
25295
|
+
formatFileSize,
|
|
25296
|
+
formatTime,
|
|
25297
|
+
getAudioCodecForFormat,
|
|
25298
|
+
getAudioErrorText,
|
|
25299
|
+
getBrowserErrorText,
|
|
25300
|
+
getBrowserInfo,
|
|
25301
|
+
getBrowserName,
|
|
25302
|
+
getCameraErrorText,
|
|
25303
|
+
getDefaultAudioCodecForFormat,
|
|
25304
|
+
getDefaultConfigForFormat,
|
|
25305
|
+
getFormatCompatibilityPolicy,
|
|
25306
|
+
getPresetAudioBitrateForFormat,
|
|
25307
|
+
getPresetTotalBitrate,
|
|
25308
|
+
getPresetVideoBitrateForFormat,
|
|
25309
|
+
isAudioMissingError,
|
|
25310
|
+
isAudioRuntimeMissingCode,
|
|
25311
|
+
isMobileDevice2 as isMobileDevice,
|
|
25312
|
+
logger,
|
|
25313
|
+
mapPresetToConfig,
|
|
25314
|
+
parseBrowserErrorLinkContent,
|
|
25315
|
+
recoverPendingUpload,
|
|
25316
|
+
requireActive,
|
|
25317
|
+
requireDefined,
|
|
25318
|
+
requireInitialized,
|
|
25319
|
+
requireMediaDevices,
|
|
25320
|
+
requireNonNull,
|
|
25321
|
+
requireProcessor,
|
|
25322
|
+
requireStream,
|
|
25323
|
+
resolveDeviceError,
|
|
25324
|
+
transcodeVideo,
|
|
25325
|
+
transcodeVideoForNativeCamera,
|
|
25326
|
+
validateBrowserSupport,
|
|
25327
|
+
validateFile
|
|
24821
25328
|
};
|