@vidtreo/recorder 0.9.1 → 0.9.2
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 -19
- package/dist/index.js +194 -81
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
7
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
8
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
9
|
+
for (let key of __getOwnPropNames(mod))
|
|
10
|
+
if (!__hasOwnProp.call(to, key))
|
|
11
|
+
__defProp(to, key, {
|
|
12
|
+
get: () => mod[key],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
18
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
19
|
+
}) : x)(function(x) {
|
|
20
|
+
if (typeof require !== "undefined")
|
|
21
|
+
return require.apply(this, arguments);
|
|
22
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
|
+
});
|
|
24
|
+
|
|
1
25
|
// src/core/utils/error-handler.ts
|
|
2
26
|
function extractErrorMessage(error) {
|
|
3
27
|
if (error instanceof Error) {
|
|
@@ -127,6 +151,9 @@ class AudioLevelAnalyzer {
|
|
|
127
151
|
return isMutedFromCallback || hasDisabledTracks;
|
|
128
152
|
}
|
|
129
153
|
}
|
|
154
|
+
// src/core/config/config-constants.ts
|
|
155
|
+
import { QUALITY_HIGH } from "mediabunny";
|
|
156
|
+
|
|
130
157
|
// src/core/processor/format-codec-mapper.ts
|
|
131
158
|
var FORMAT_DEFAULT_CODECS = {
|
|
132
159
|
mp4: "aac",
|
|
@@ -149,13 +176,11 @@ var DEFAULT_BACKEND_URL = "https://api.vidtreo.com";
|
|
|
149
176
|
var DEFAULT_TRANSCODE_CONFIG = Object.freeze({
|
|
150
177
|
format: "mp4",
|
|
151
178
|
fps: 30,
|
|
152
|
-
width:
|
|
153
|
-
height:
|
|
154
|
-
bitrate:
|
|
155
|
-
audioCodec:
|
|
156
|
-
audioBitrate:
|
|
157
|
-
preset: "medium",
|
|
158
|
-
packetCount: 1200
|
|
179
|
+
width: 1920,
|
|
180
|
+
height: 1080,
|
|
181
|
+
bitrate: QUALITY_HIGH,
|
|
182
|
+
audioCodec: "aac",
|
|
183
|
+
audioBitrate: 96000
|
|
159
184
|
});
|
|
160
185
|
function getDefaultConfigForFormat(format) {
|
|
161
186
|
return {
|
|
@@ -165,23 +190,21 @@ function getDefaultConfigForFormat(format) {
|
|
|
165
190
|
};
|
|
166
191
|
}
|
|
167
192
|
// src/core/config/preset-mapper.ts
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
193
|
+
import {
|
|
194
|
+
QUALITY_HIGH as QUALITY_HIGH2,
|
|
195
|
+
QUALITY_LOW,
|
|
196
|
+
QUALITY_MEDIUM,
|
|
197
|
+
QUALITY_VERY_HIGH
|
|
198
|
+
} from "mediabunny";
|
|
199
|
+
var QUALITY_MAP = {
|
|
200
|
+
sd: QUALITY_LOW,
|
|
201
|
+
hd: QUALITY_MEDIUM,
|
|
202
|
+
fhd: QUALITY_HIGH2,
|
|
203
|
+
"4k": QUALITY_VERY_HIGH
|
|
173
204
|
};
|
|
174
205
|
var AUDIO_BITRATE = 128000;
|
|
175
|
-
var PACKET_COUNT_MAP = {
|
|
176
|
-
sd: 800,
|
|
177
|
-
hd: 1200,
|
|
178
|
-
fhd: 2000,
|
|
179
|
-
"4k": 4000
|
|
180
|
-
};
|
|
181
|
-
var DEFAULT_FPS = 30;
|
|
182
|
-
var DEFAULT_PRESET = "medium";
|
|
183
206
|
function mapPresetToConfig(preset, maxWidth, maxHeight, format = "mp4") {
|
|
184
|
-
if (!(preset in
|
|
207
|
+
if (!(preset in QUALITY_MAP)) {
|
|
185
208
|
throw new Error(`Invalid preset: ${preset}`);
|
|
186
209
|
}
|
|
187
210
|
if (typeof maxWidth !== "number" || maxWidth <= 0) {
|
|
@@ -193,13 +216,10 @@ function mapPresetToConfig(preset, maxWidth, maxHeight, format = "mp4") {
|
|
|
193
216
|
const audioCodec = getDefaultAudioCodecForFormat(format);
|
|
194
217
|
return {
|
|
195
218
|
format,
|
|
196
|
-
fps: DEFAULT_FPS,
|
|
197
219
|
width: maxWidth,
|
|
198
220
|
height: maxHeight,
|
|
199
|
-
bitrate:
|
|
221
|
+
bitrate: QUALITY_MAP[preset],
|
|
200
222
|
audioCodec,
|
|
201
|
-
preset: DEFAULT_PRESET,
|
|
202
|
-
packetCount: PACKET_COUNT_MAP[preset],
|
|
203
223
|
audioBitrate: AUDIO_BITRATE
|
|
204
224
|
};
|
|
205
225
|
}
|
|
@@ -311,8 +331,12 @@ class ConfigManager {
|
|
|
311
331
|
apiKey,
|
|
312
332
|
backendUrl: normalizedBackendUrl
|
|
313
333
|
});
|
|
314
|
-
this.
|
|
315
|
-
|
|
334
|
+
this.configService.fetchConfig().then((config) => {
|
|
335
|
+
this.currentConfig = config;
|
|
336
|
+
this.configFetched = true;
|
|
337
|
+
}).catch(() => {
|
|
338
|
+
this.configFetched = false;
|
|
339
|
+
});
|
|
316
340
|
}
|
|
317
341
|
async fetchConfig() {
|
|
318
342
|
if (!this.configService) {
|
|
@@ -1379,9 +1403,9 @@ class SourceSwitchManager {
|
|
|
1379
1403
|
|
|
1380
1404
|
// src/core/stream/stream-constants.ts
|
|
1381
1405
|
var DEFAULT_CAMERA_CONSTRAINTS = Object.freeze({
|
|
1382
|
-
width: { ideal: DEFAULT_TRANSCODE_CONFIG.width },
|
|
1383
|
-
height: { ideal: DEFAULT_TRANSCODE_CONFIG.height },
|
|
1384
|
-
frameRate: { ideal: DEFAULT_TRANSCODE_CONFIG.fps }
|
|
1406
|
+
width: { ideal: DEFAULT_TRANSCODE_CONFIG.width || 1920 },
|
|
1407
|
+
height: { ideal: DEFAULT_TRANSCODE_CONFIG.height || 1080 },
|
|
1408
|
+
frameRate: { ideal: DEFAULT_TRANSCODE_CONFIG.fps || 30 }
|
|
1385
1409
|
});
|
|
1386
1410
|
var DEFAULT_STREAM_CONFIG = Object.freeze({
|
|
1387
1411
|
video: DEFAULT_CAMERA_CONSTRAINTS,
|
|
@@ -2647,6 +2671,30 @@ function isScreenCaptureStream(stream) {
|
|
|
2647
2671
|
return "displaySurface" in settings || videoTrack.label.toLowerCase().includes("screen") || videoTrack.label.toLowerCase().includes("display");
|
|
2648
2672
|
}
|
|
2649
2673
|
|
|
2674
|
+
// src/core/processor/codec-detector.ts
|
|
2675
|
+
async function detectBestCodec(width, height, bitrate) {
|
|
2676
|
+
try {
|
|
2677
|
+
const { canEncodeVideo } = await import("mediabunny");
|
|
2678
|
+
if (typeof canEncodeVideo === "function") {
|
|
2679
|
+
const checkOptions = {};
|
|
2680
|
+
if (width !== undefined) {
|
|
2681
|
+
checkOptions.width = width;
|
|
2682
|
+
}
|
|
2683
|
+
if (height !== undefined) {
|
|
2684
|
+
checkOptions.height = height;
|
|
2685
|
+
}
|
|
2686
|
+
if (bitrate !== undefined) {
|
|
2687
|
+
checkOptions.bitrate = bitrate;
|
|
2688
|
+
}
|
|
2689
|
+
const hevcSupported = await canEncodeVideo("hevc", checkOptions);
|
|
2690
|
+
if (hevcSupported) {
|
|
2691
|
+
return "hevc";
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
} catch {}
|
|
2695
|
+
return "avc";
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2650
2698
|
// src/core/processor/worker/recorder-worker.code.ts
|
|
2651
2699
|
var workerCode = `// ../../node_modules/mediabunny/dist/modules/src/misc.js
|
|
2652
2700
|
/*!
|
|
@@ -8366,6 +8414,7 @@ class Quality {
|
|
|
8366
8414
|
return Math.round(finalBitrate / 1000) * 1000;
|
|
8367
8415
|
}
|
|
8368
8416
|
}
|
|
8417
|
+
var QUALITY_HIGH = /* @__PURE__ */ new Quality(2);
|
|
8369
8418
|
|
|
8370
8419
|
// ../../node_modules/mediabunny/dist/modules/src/media-source.js
|
|
8371
8420
|
/*!
|
|
@@ -9731,6 +9780,8 @@ class RecorderWorker {
|
|
|
9731
9780
|
baseVideoTimestamp = null;
|
|
9732
9781
|
frameCount = 0;
|
|
9733
9782
|
config = null;
|
|
9783
|
+
lastKeyFrameTimestamp = 0;
|
|
9784
|
+
forceNextKeyFrame = false;
|
|
9734
9785
|
videoProcessingActive = false;
|
|
9735
9786
|
audioProcessingActive = false;
|
|
9736
9787
|
isStopping = false;
|
|
@@ -9819,54 +9870,50 @@ class RecorderWorker {
|
|
|
9819
9870
|
}
|
|
9820
9871
|
this.sendError(new Error(\`Unknown message type: \${message.type}\`));
|
|
9821
9872
|
};
|
|
9822
|
-
|
|
9873
|
+
validateConfig(config) {
|
|
9823
9874
|
requireDefined(config, "Transcode config is required");
|
|
9824
|
-
if (config.width
|
|
9825
|
-
throw new Error("Video
|
|
9875
|
+
if (config.width !== undefined && config.width <= 0) {
|
|
9876
|
+
throw new Error("Video width must be greater than zero");
|
|
9877
|
+
}
|
|
9878
|
+
if (config.height !== undefined && config.height <= 0) {
|
|
9879
|
+
throw new Error("Video height must be greater than zero");
|
|
9826
9880
|
}
|
|
9827
|
-
if (config.fps <= 0) {
|
|
9881
|
+
if (config.fps !== undefined && config.fps <= 0) {
|
|
9828
9882
|
throw new Error("Frame rate must be greater than zero");
|
|
9829
9883
|
}
|
|
9830
|
-
if (config.bitrate <= 0) {
|
|
9884
|
+
if (config.bitrate !== undefined && typeof config.bitrate === "number" && config.bitrate <= 0) {
|
|
9831
9885
|
throw new Error("Bitrate must be greater than zero");
|
|
9832
9886
|
}
|
|
9833
9887
|
if (config.keyFrameInterval <= 0) {
|
|
9834
9888
|
throw new Error("Key frame interval must be greater than zero");
|
|
9835
9889
|
}
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
width: config.width,
|
|
9841
|
-
height: config.height,
|
|
9842
|
-
fps: config.fps,
|
|
9843
|
-
bitrate: config.bitrate
|
|
9844
|
-
},
|
|
9845
|
-
hasOverlayConfig: !!overlayConfig,
|
|
9846
|
-
overlayConfig
|
|
9847
|
-
});
|
|
9848
|
-
this.isStopping = false;
|
|
9849
|
-
this.isFinalized = false;
|
|
9850
|
-
if (this.output) {
|
|
9851
|
-
logger.debug("[RecorderWorker] Cleaning up existing output");
|
|
9852
|
-
await this.cleanup();
|
|
9890
|
+
}
|
|
9891
|
+
validateFormat(format) {
|
|
9892
|
+
if (format !== "mp4") {
|
|
9893
|
+
throw new Error(\`Format \${format} is not yet supported in worker. Only MP4 is currently supported.\`);
|
|
9853
9894
|
}
|
|
9895
|
+
}
|
|
9896
|
+
initializeRecordingState(config) {
|
|
9854
9897
|
this.config = config;
|
|
9855
|
-
this.frameRate = config.fps;
|
|
9898
|
+
this.frameRate = config.fps || 30;
|
|
9856
9899
|
this.isPaused = false;
|
|
9857
9900
|
this.isMuted = false;
|
|
9858
9901
|
this.lastVideoTimestamp = 0;
|
|
9859
9902
|
this.lastAudioTimestamp = 0;
|
|
9860
9903
|
this.baseVideoTimestamp = null;
|
|
9861
9904
|
this.frameCount = 0;
|
|
9905
|
+
this.lastKeyFrameTimestamp = 0;
|
|
9906
|
+
this.forceNextKeyFrame = false;
|
|
9862
9907
|
this.pausedDuration = 0;
|
|
9863
9908
|
this.pauseStartedAt = null;
|
|
9864
|
-
this.overlayConfig = overlayConfig ? { enabled: overlayConfig.enabled, text: overlayConfig.text } : null;
|
|
9865
9909
|
this.overlayCanvas = null;
|
|
9866
9910
|
this.hiddenIntervals = [];
|
|
9867
9911
|
this.currentHiddenIntervalStart = null;
|
|
9868
|
-
this.recordingStartTime = overlayConfig?.recordingStartTime !== undefined ? overlayConfig.recordingStartTime / 1000 : performance.now() / 1000;
|
|
9869
9912
|
this.pendingVisibilityUpdates = [];
|
|
9913
|
+
}
|
|
9914
|
+
setupOverlayConfig(overlayConfig) {
|
|
9915
|
+
this.overlayConfig = overlayConfig ? { enabled: overlayConfig.enabled, text: overlayConfig.text } : null;
|
|
9916
|
+
this.recordingStartTime = overlayConfig?.recordingStartTime !== undefined ? overlayConfig.recordingStartTime / 1000 : performance.now() / 1000;
|
|
9870
9917
|
const logData = {
|
|
9871
9918
|
hasOverlayConfig: !!this.overlayConfig,
|
|
9872
9919
|
overlayEnabled: this.overlayConfig?.enabled,
|
|
@@ -9874,15 +9921,13 @@ class RecorderWorker {
|
|
|
9874
9921
|
recordingStartTime: this.recordingStartTime
|
|
9875
9922
|
};
|
|
9876
9923
|
logger.debug("[RecorderWorker] Overlay config initialized", logData);
|
|
9924
|
+
}
|
|
9925
|
+
createOutput() {
|
|
9877
9926
|
const writable = new WritableStream({
|
|
9878
9927
|
write: (chunk) => {
|
|
9879
9928
|
this.sendChunk(chunk.data, chunk.position);
|
|
9880
9929
|
}
|
|
9881
9930
|
});
|
|
9882
|
-
const format = config.format || "mp4";
|
|
9883
|
-
if (format !== "mp4") {
|
|
9884
|
-
throw new Error(\`Format \${format} is not yet supported in worker. Only MP4 is currently supported.\`);
|
|
9885
|
-
}
|
|
9886
9931
|
this.output = new Output({
|
|
9887
9932
|
format: new Mp4OutputFormat({
|
|
9888
9933
|
fastStart: "fragmented"
|
|
@@ -9892,27 +9937,75 @@ class RecorderWorker {
|
|
|
9892
9937
|
chunkSize: CHUNK_SIZE
|
|
9893
9938
|
})
|
|
9894
9939
|
});
|
|
9895
|
-
|
|
9940
|
+
}
|
|
9941
|
+
createVideoSource(config) {
|
|
9942
|
+
const fps = config.fps || 30;
|
|
9943
|
+
const keyFrameIntervalSeconds = config.keyFrameInterval / fps;
|
|
9944
|
+
const videoSourceOptions = {
|
|
9896
9945
|
codec: config.codec,
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9946
|
+
sizeChangeBehavior: "passThrough",
|
|
9947
|
+
bitrateMode: "variable",
|
|
9948
|
+
latencyMode: "quality",
|
|
9949
|
+
contentHint: "detail",
|
|
9950
|
+
hardwareAcceleration: "prefer-hardware",
|
|
9951
|
+
keyFrameInterval: keyFrameIntervalSeconds,
|
|
9952
|
+
bitrate: config.bitrate ?? QUALITY_HIGH
|
|
9953
|
+
};
|
|
9954
|
+
this.videoSource = new VideoSampleSource(videoSourceOptions);
|
|
9955
|
+
const output = requireNonNull(this.output, "Output must be initialized before adding video track");
|
|
9956
|
+
const trackOptions = {};
|
|
9957
|
+
if (fps !== undefined) {
|
|
9958
|
+
trackOptions.frameRate = fps;
|
|
9903
9959
|
}
|
|
9960
|
+
output.addVideoTrack(this.videoSource, trackOptions);
|
|
9961
|
+
}
|
|
9962
|
+
setupAudioSource(audioStream, config) {
|
|
9904
9963
|
if (audioStream && config.audioBitrate && config.audioCodec) {
|
|
9905
9964
|
if (config.audioBitrate <= 0) {
|
|
9906
9965
|
throw new Error("Audio bitrate must be greater than zero");
|
|
9907
9966
|
}
|
|
9908
9967
|
this.audioSource = new AudioSampleSource({
|
|
9909
9968
|
codec: config.audioCodec,
|
|
9910
|
-
bitrate: config.audioBitrate
|
|
9969
|
+
bitrate: config.audioBitrate,
|
|
9970
|
+
bitrateMode: "variable"
|
|
9911
9971
|
});
|
|
9912
|
-
|
|
9972
|
+
const output = requireNonNull(this.output, "Output must be initialized before adding audio track");
|
|
9973
|
+
output.addAudioTrack(this.audioSource);
|
|
9913
9974
|
this.setupAudioProcessing(audioStream);
|
|
9914
9975
|
}
|
|
9915
|
-
|
|
9976
|
+
}
|
|
9977
|
+
async handleStart(videoStream, audioStream, config, overlayConfig) {
|
|
9978
|
+
this.validateConfig(config);
|
|
9979
|
+
logger.debug("[RecorderWorker] handleStart called", {
|
|
9980
|
+
hasVideoStream: !!videoStream,
|
|
9981
|
+
hasAudioStream: !!audioStream,
|
|
9982
|
+
config: {
|
|
9983
|
+
width: config.width,
|
|
9984
|
+
height: config.height,
|
|
9985
|
+
fps: config.fps,
|
|
9986
|
+
bitrate: config.bitrate
|
|
9987
|
+
},
|
|
9988
|
+
hasOverlayConfig: !!overlayConfig,
|
|
9989
|
+
overlayConfig
|
|
9990
|
+
});
|
|
9991
|
+
this.isStopping = false;
|
|
9992
|
+
this.isFinalized = false;
|
|
9993
|
+
if (this.output) {
|
|
9994
|
+
logger.debug("[RecorderWorker] Cleaning up existing output");
|
|
9995
|
+
await this.cleanup();
|
|
9996
|
+
}
|
|
9997
|
+
this.initializeRecordingState(config);
|
|
9998
|
+
this.setupOverlayConfig(overlayConfig);
|
|
9999
|
+
const format = config.format || "mp4";
|
|
10000
|
+
this.validateFormat(format);
|
|
10001
|
+
this.createOutput();
|
|
10002
|
+
this.createVideoSource(config);
|
|
10003
|
+
if (videoStream) {
|
|
10004
|
+
this.setupVideoProcessing(videoStream);
|
|
10005
|
+
}
|
|
10006
|
+
this.setupAudioSource(audioStream, config);
|
|
10007
|
+
const output = requireNonNull(this.output, "Output must be initialized before starting");
|
|
10008
|
+
await output.start();
|
|
9916
10009
|
this.startBufferUpdates();
|
|
9917
10010
|
this.sendReady();
|
|
9918
10011
|
this.sendStateChange("recording");
|
|
@@ -10157,8 +10250,6 @@ class RecorderWorker {
|
|
|
10157
10250
|
}
|
|
10158
10251
|
}
|
|
10159
10252
|
}
|
|
10160
|
-
const keyFrameInterval = config.keyFrameInterval > 0 ? config.keyFrameInterval : 5;
|
|
10161
|
-
const isKeyFrame = this.frameCount % keyFrameInterval === 0;
|
|
10162
10253
|
const maxLead = 0.05;
|
|
10163
10254
|
const maxLag = 0.1;
|
|
10164
10255
|
const targetAudio = this.lastAudioTimestamp;
|
|
@@ -10170,6 +10261,10 @@ class RecorderWorker {
|
|
|
10170
10261
|
}
|
|
10171
10262
|
const monotonicTimestamp = this.lastVideoTimestamp + frameDuration;
|
|
10172
10263
|
const finalTimestamp = adjustedTimestamp >= monotonicTimestamp ? adjustedTimestamp : monotonicTimestamp;
|
|
10264
|
+
const keyFrameIntervalFrames = config.keyFrameInterval > 0 ? config.keyFrameInterval : 5;
|
|
10265
|
+
const keyFrameIntervalSeconds = keyFrameIntervalFrames / this.frameRate;
|
|
10266
|
+
const timeSinceLastKeyFrame = finalTimestamp - this.lastKeyFrameTimestamp;
|
|
10267
|
+
const isKeyFrame = this.forceNextKeyFrame || timeSinceLastKeyFrame >= keyFrameIntervalSeconds || this.frameCount % keyFrameIntervalFrames === 0;
|
|
10173
10268
|
this.driftOffset *= 0.5;
|
|
10174
10269
|
const sample = new VideoSample(frameToProcess, {
|
|
10175
10270
|
timestamp: finalTimestamp,
|
|
@@ -10184,6 +10279,10 @@ class RecorderWorker {
|
|
|
10184
10279
|
if (!addError) {
|
|
10185
10280
|
this.frameCount += 1;
|
|
10186
10281
|
this.lastVideoTimestamp = finalTimestamp;
|
|
10282
|
+
if (isKeyFrame) {
|
|
10283
|
+
this.lastKeyFrameTimestamp = finalTimestamp;
|
|
10284
|
+
this.forceNextKeyFrame = false;
|
|
10285
|
+
}
|
|
10187
10286
|
if (this.frameCount % 90 === 0 && this.audioProcessingActive) {
|
|
10188
10287
|
const avDrift = this.lastAudioTimestamp - this.lastVideoTimestamp;
|
|
10189
10288
|
logger.debug("[RecorderWorker] AV drift metrics", {
|
|
@@ -10460,6 +10559,7 @@ class RecorderWorker {
|
|
|
10460
10559
|
const previousVideoTimestamp = this.lastVideoTimestamp;
|
|
10461
10560
|
this.lastVideoTimestamp = continuationTimestamp;
|
|
10462
10561
|
this.frameCount = 0;
|
|
10562
|
+
this.forceNextKeyFrame = true;
|
|
10463
10563
|
logger.debug("[RecorderWorker] handleSwitchSource - preserving baseVideoTimestamp", {
|
|
10464
10564
|
continuationTimestamp,
|
|
10465
10565
|
lastVideoTimestamp: this.lastVideoTimestamp,
|
|
@@ -10512,6 +10612,8 @@ class RecorderWorker {
|
|
|
10512
10612
|
this.lastAudioTimestamp = 0;
|
|
10513
10613
|
this.baseVideoTimestamp = null;
|
|
10514
10614
|
this.frameCount = 0;
|
|
10615
|
+
this.lastKeyFrameTimestamp = 0;
|
|
10616
|
+
this.forceNextKeyFrame = false;
|
|
10515
10617
|
this.totalSize = 0;
|
|
10516
10618
|
this.pausedDuration = 0;
|
|
10517
10619
|
this.pauseStartedAt = null;
|
|
@@ -10566,7 +10668,6 @@ new RecorderWorker;
|
|
|
10566
10668
|
|
|
10567
10669
|
// src/core/processor/worker-processor.ts
|
|
10568
10670
|
var KEY_FRAME_INTERVAL = 5;
|
|
10569
|
-
var H264_CODEC = "avc";
|
|
10570
10671
|
var workerBlobUrl = null;
|
|
10571
10672
|
function getWorkerUrl() {
|
|
10572
10673
|
if (workerBlobUrl) {
|
|
@@ -10687,20 +10788,21 @@ class WorkerProcessor {
|
|
|
10687
10788
|
const format = config.format || "mp4";
|
|
10688
10789
|
const audioCodec = config.audioCodec || getDefaultAudioCodecForFormat(format);
|
|
10689
10790
|
const isScreenCapture = isScreenCaptureStream(stream);
|
|
10690
|
-
const
|
|
10791
|
+
const codec = config.codec || await detectBestCodec(config.width, config.height, config.bitrate);
|
|
10691
10792
|
logger.debug("[WorkerProcessor] Starting processing", {
|
|
10692
10793
|
isScreenCapture,
|
|
10693
|
-
|
|
10694
|
-
|
|
10794
|
+
fps: config.fps,
|
|
10795
|
+
codec,
|
|
10796
|
+
bitrate: config.bitrate
|
|
10695
10797
|
});
|
|
10696
10798
|
const workerConfig = {
|
|
10697
10799
|
width: config.width,
|
|
10698
10800
|
height: config.height,
|
|
10699
|
-
fps:
|
|
10801
|
+
fps: config.fps,
|
|
10700
10802
|
bitrate: config.bitrate,
|
|
10701
10803
|
audioCodec,
|
|
10702
10804
|
audioBitrate: config.audioBitrate,
|
|
10703
|
-
codec
|
|
10805
|
+
codec,
|
|
10704
10806
|
keyFrameInterval: KEY_FRAME_INTERVAL,
|
|
10705
10807
|
format
|
|
10706
10808
|
};
|
|
@@ -11797,13 +11899,24 @@ function getMimeTypeForFormat(format) {
|
|
|
11797
11899
|
function createConversionOptions(config) {
|
|
11798
11900
|
const audioCodec = getAudioCodecForFormat(config.format, config.audioCodec);
|
|
11799
11901
|
const video = {
|
|
11800
|
-
width: config.width,
|
|
11801
|
-
height: config.height,
|
|
11802
11902
|
fit: "contain",
|
|
11803
|
-
frameRate: config.fps,
|
|
11804
|
-
bitrate: config.bitrate,
|
|
11805
11903
|
forceTranscode: true
|
|
11806
11904
|
};
|
|
11905
|
+
if (config.width !== undefined) {
|
|
11906
|
+
video.width = config.width;
|
|
11907
|
+
}
|
|
11908
|
+
if (config.height !== undefined) {
|
|
11909
|
+
video.height = config.height;
|
|
11910
|
+
}
|
|
11911
|
+
if (config.fps !== undefined) {
|
|
11912
|
+
video.frameRate = config.fps;
|
|
11913
|
+
}
|
|
11914
|
+
if (config.bitrate !== undefined) {
|
|
11915
|
+
video.bitrate = config.bitrate;
|
|
11916
|
+
}
|
|
11917
|
+
if (config.codec !== undefined) {
|
|
11918
|
+
video.codec = config.codec;
|
|
11919
|
+
}
|
|
11807
11920
|
const audio = {
|
|
11808
11921
|
codec: audioCodec,
|
|
11809
11922
|
forceTranscode: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidtreo/recorder",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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",
|