minerender 2.1.0-alpha.85 → 2.1.0-alpha.86
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/browser/index.d.mts +59 -3
- package/dist/browser/index.d.ts +59 -3
- package/dist/browser/index.js +165 -4
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +165 -4
- package/dist/browser/index.mjs.map +1 -1
- package/dist/bundle.js +168 -4
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +59 -3
- package/dist/node/index.d.ts +59 -3
- package/dist/node/index.js +166 -4
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +165 -4
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.mjs
CHANGED
|
@@ -22078,6 +22078,131 @@ function copyDataTexture(source) {
|
|
|
22078
22078
|
return new Texture3().copy(copySource);
|
|
22079
22079
|
}
|
|
22080
22080
|
|
|
22081
|
+
// src/export/VideoExporter.ts
|
|
22082
|
+
var VideoExporter = class {
|
|
22083
|
+
constructor(source, options) {
|
|
22084
|
+
this.source = source;
|
|
22085
|
+
this.chunks = [];
|
|
22086
|
+
this.settled = false;
|
|
22087
|
+
this.stopping = false;
|
|
22088
|
+
this.onStart = () => {
|
|
22089
|
+
this.timer = setTimeout(() => {
|
|
22090
|
+
this.stopping = true;
|
|
22091
|
+
try {
|
|
22092
|
+
this.recorder.stop();
|
|
22093
|
+
} catch (error) {
|
|
22094
|
+
this.cancel(error);
|
|
22095
|
+
}
|
|
22096
|
+
}, this.duration * 1e3);
|
|
22097
|
+
};
|
|
22098
|
+
this.onData = (event) => {
|
|
22099
|
+
if (event.data.size) this.chunks.push(event.data);
|
|
22100
|
+
};
|
|
22101
|
+
this.onStop = () => {
|
|
22102
|
+
if (!this.stopping) return this.cancel(new Error("Video recording stopped before its duration elapsed"));
|
|
22103
|
+
if (!this.chunks.length) return this.cancel(new Error("The browser produced no video data"));
|
|
22104
|
+
const mimeType = this.chunks.find((chunk) => chunk.type)?.type || this.recorder.mimeType;
|
|
22105
|
+
const blob = new Blob(this.chunks, { type: mimeType });
|
|
22106
|
+
this.settled = true;
|
|
22107
|
+
this.cleanup();
|
|
22108
|
+
this.resolve(blob);
|
|
22109
|
+
};
|
|
22110
|
+
this.onError = (event) => {
|
|
22111
|
+
this.cancel(event.error ?? new Error("Video recording failed"));
|
|
22112
|
+
};
|
|
22113
|
+
this.onAbort = () => {
|
|
22114
|
+
this.cancel(this.signal.reason);
|
|
22115
|
+
};
|
|
22116
|
+
this.onTrackEnded = () => {
|
|
22117
|
+
this.cancel(new Error("The video capture stream stopped producing frames"));
|
|
22118
|
+
};
|
|
22119
|
+
const fps = options.fps ?? 30;
|
|
22120
|
+
if (!Number.isFinite(options.duration) || options.duration <= 0 || options.duration * 1e3 > 2147483647) {
|
|
22121
|
+
throw new RangeError("Video duration must be greater than zero and at most 2147483.647 seconds");
|
|
22122
|
+
}
|
|
22123
|
+
if (!Number.isFinite(fps) || fps <= 0) throw new RangeError("Video fps must be a finite positive number");
|
|
22124
|
+
const bitrate = options.videoBitsPerSecond;
|
|
22125
|
+
if (bitrate !== void 0 && (!Number.isInteger(bitrate) || bitrate <= 0 || bitrate > 4294967295)) {
|
|
22126
|
+
throw new RangeError("Video bitrate must be a positive unsigned 32-bit integer");
|
|
22127
|
+
}
|
|
22128
|
+
if (!source.width || !source.height) throw new Error("Cannot export video from an empty canvas");
|
|
22129
|
+
options.signal?.throwIfAborted();
|
|
22130
|
+
if (typeof document === "undefined" || typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
|
|
22131
|
+
throw new Error("Video export requires a browser with MediaRecorder support");
|
|
22132
|
+
}
|
|
22133
|
+
const mimeTypes = options.mimeType === void 0 ? ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"] : [options.mimeType];
|
|
22134
|
+
const mimeType = mimeTypes.find((type) => type.startsWith("video/") && MediaRecorder.isTypeSupported(type));
|
|
22135
|
+
if (!mimeType) throw new Error("The browser does not support the requested video format");
|
|
22136
|
+
this.canvas = document.createElement("canvas");
|
|
22137
|
+
this.canvas.width = source.width;
|
|
22138
|
+
this.canvas.height = source.height;
|
|
22139
|
+
if (typeof this.canvas.captureStream !== "function") throw new Error("Video export requires canvas captureStream support");
|
|
22140
|
+
const context = this.canvas.getContext("2d", { alpha: false });
|
|
22141
|
+
if (!context) throw new Error("Could not create a canvas for video export");
|
|
22142
|
+
this.context = context;
|
|
22143
|
+
this.signal = options.signal;
|
|
22144
|
+
this.duration = options.duration;
|
|
22145
|
+
this.result = new Promise((resolve, reject) => {
|
|
22146
|
+
this.resolve = resolve;
|
|
22147
|
+
this.reject = reject;
|
|
22148
|
+
});
|
|
22149
|
+
try {
|
|
22150
|
+
this.stream = this.canvas.captureStream(fps);
|
|
22151
|
+
this.recorder = new MediaRecorder(this.stream, { mimeType, videoBitsPerSecond: bitrate });
|
|
22152
|
+
this.recorder.addEventListener("start", this.onStart);
|
|
22153
|
+
this.recorder.addEventListener("dataavailable", this.onData);
|
|
22154
|
+
this.recorder.addEventListener("stop", this.onStop);
|
|
22155
|
+
this.recorder.addEventListener("error", this.onError);
|
|
22156
|
+
for (const track of this.stream.getTracks()) {
|
|
22157
|
+
track.addEventListener("ended", this.onTrackEnded);
|
|
22158
|
+
}
|
|
22159
|
+
this.signal?.addEventListener("abort", this.onAbort, { once: true });
|
|
22160
|
+
this.recorder.start();
|
|
22161
|
+
} catch (error) {
|
|
22162
|
+
this.cancel(error);
|
|
22163
|
+
}
|
|
22164
|
+
}
|
|
22165
|
+
/** Copies a freshly drawn source frame before WebGL can clear its drawing buffer. */
|
|
22166
|
+
capture() {
|
|
22167
|
+
if (this.settled || this.stopping) return;
|
|
22168
|
+
try {
|
|
22169
|
+
this.context.fillStyle = "#000000";
|
|
22170
|
+
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
22171
|
+
this.context.drawImage(this.source, 0, 0, this.canvas.width, this.canvas.height);
|
|
22172
|
+
} catch (error) {
|
|
22173
|
+
this.cancel(error);
|
|
22174
|
+
}
|
|
22175
|
+
}
|
|
22176
|
+
/** Cancels recording, releases its capture stream, and rejects {@link result}. */
|
|
22177
|
+
cancel(reason = new DOMException("Video export cancelled", "AbortError")) {
|
|
22178
|
+
if (this.settled) return;
|
|
22179
|
+
this.settled = true;
|
|
22180
|
+
this.cleanup();
|
|
22181
|
+
this.reject(reason);
|
|
22182
|
+
}
|
|
22183
|
+
cleanup() {
|
|
22184
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
22185
|
+
this.signal?.removeEventListener("abort", this.onAbort);
|
|
22186
|
+
if (this.recorder) {
|
|
22187
|
+
this.recorder.removeEventListener("start", this.onStart);
|
|
22188
|
+
this.recorder.removeEventListener("dataavailable", this.onData);
|
|
22189
|
+
this.recorder.removeEventListener("stop", this.onStop);
|
|
22190
|
+
this.recorder.removeEventListener("error", this.onError);
|
|
22191
|
+
if (this.recorder.state !== "inactive") {
|
|
22192
|
+
try {
|
|
22193
|
+
this.recorder.stop();
|
|
22194
|
+
} catch {
|
|
22195
|
+
}
|
|
22196
|
+
}
|
|
22197
|
+
}
|
|
22198
|
+
for (const track of this.stream?.getTracks() ?? []) {
|
|
22199
|
+
track.removeEventListener("ended", this.onTrackEnded);
|
|
22200
|
+
track.stop();
|
|
22201
|
+
}
|
|
22202
|
+
this.chunks.length = 0;
|
|
22203
|
+
}
|
|
22204
|
+
};
|
|
22205
|
+
|
|
22081
22206
|
// src/gui/GuiHelper.ts
|
|
22082
22207
|
init_AssetKey();
|
|
22083
22208
|
init_ModelTextures();
|
|
@@ -27397,6 +27522,9 @@ var Renderer = class _Renderer {
|
|
|
27397
27522
|
this._inAnimationLoop = true;
|
|
27398
27523
|
try {
|
|
27399
27524
|
this.animate(time);
|
|
27525
|
+
} catch (error) {
|
|
27526
|
+
this._videoExporter?.cancel(error);
|
|
27527
|
+
throw error;
|
|
27400
27528
|
} finally {
|
|
27401
27529
|
this._inAnimationLoop = false;
|
|
27402
27530
|
}
|
|
@@ -27631,16 +27759,18 @@ var Renderer = class _Renderer {
|
|
|
27631
27759
|
if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
|
|
27632
27760
|
};
|
|
27633
27761
|
}
|
|
27634
|
-
/** Starts or resumes the animation loop and registered frame callbacks. */
|
|
27762
|
+
/** Starts or resumes the animation loop and registered frame callbacks. Has no effect while running. */
|
|
27635
27763
|
start() {
|
|
27636
|
-
if (this._disposed) return;
|
|
27764
|
+
if (this._disposed || this._running) return;
|
|
27637
27765
|
this.stop();
|
|
27638
27766
|
this._running = true;
|
|
27639
27767
|
if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
|
|
27640
27768
|
}
|
|
27641
|
-
/** Pauses rendering and frame callbacks. Call {@link start} to resume. */
|
|
27769
|
+
/** Pauses rendering and frame callbacks, and cancels video export. Call {@link start} to resume. */
|
|
27642
27770
|
stop() {
|
|
27643
27771
|
if (this._disposed) return;
|
|
27772
|
+
this._videoExporter?.cancel();
|
|
27773
|
+
this._videoExporter = void 0;
|
|
27644
27774
|
this._running = false;
|
|
27645
27775
|
if (this._inAnimationLoop) {
|
|
27646
27776
|
queueMicrotask(() => {
|
|
@@ -27702,7 +27832,7 @@ var Renderer = class _Renderer {
|
|
|
27702
27832
|
this._controls.update();
|
|
27703
27833
|
}
|
|
27704
27834
|
if (!this._running) return;
|
|
27705
|
-
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size) return;
|
|
27835
|
+
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
|
|
27706
27836
|
const interval = this._frameInterval;
|
|
27707
27837
|
if (interval) {
|
|
27708
27838
|
const next = this._nextFrameTime;
|
|
@@ -27727,6 +27857,7 @@ var Renderer = class _Renderer {
|
|
|
27727
27857
|
} else {
|
|
27728
27858
|
this.renderer.render(this.scene, this.camera);
|
|
27729
27859
|
}
|
|
27860
|
+
this._videoExporter?.capture();
|
|
27730
27861
|
this.dirty = false;
|
|
27731
27862
|
if (this._stats) {
|
|
27732
27863
|
this._stats.end();
|
|
@@ -27747,6 +27878,35 @@ var Renderer = class _Renderer {
|
|
|
27747
27878
|
const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
|
|
27748
27879
|
return canvas.toDataURL(mime, quality);
|
|
27749
27880
|
}
|
|
27881
|
+
/**
|
|
27882
|
+
* Records a silent browser video in real time, including frame callbacks and animated textures.
|
|
27883
|
+
* Keep the tab visible; browser scheduling and `render.fpsLimit` limit the capture rate.
|
|
27884
|
+
* The video keeps the initial drawing-buffer size and fills transparent pixels with black.
|
|
27885
|
+
* Temporarily starts a stopped renderer. Stop, disposal, or `options.signal` cancels the export.
|
|
27886
|
+
* Only one export can run at a time. Use the returned Blob's type to select a file extension.
|
|
27887
|
+
*/
|
|
27888
|
+
async toVideo(options) {
|
|
27889
|
+
if (this._disposed) throw new Error("Cannot export a video from a disposed renderer");
|
|
27890
|
+
if (this._videoExporter) throw new Error("A video export is already running");
|
|
27891
|
+
const exporter = new VideoExporter(this.renderer.domElement, options);
|
|
27892
|
+
const wasRunning = this._running;
|
|
27893
|
+
try {
|
|
27894
|
+
try {
|
|
27895
|
+
if (!wasRunning) this.start();
|
|
27896
|
+
this._videoExporter = exporter;
|
|
27897
|
+
if (this._controls?.enabled) this._controls.update();
|
|
27898
|
+
this.drawFrame();
|
|
27899
|
+
} catch (error) {
|
|
27900
|
+
exporter.cancel(error);
|
|
27901
|
+
}
|
|
27902
|
+
return await exporter.result;
|
|
27903
|
+
} finally {
|
|
27904
|
+
if (this._videoExporter === exporter) {
|
|
27905
|
+
this._videoExporter = void 0;
|
|
27906
|
+
if (!wasRunning) this.stop();
|
|
27907
|
+
}
|
|
27908
|
+
}
|
|
27909
|
+
}
|
|
27750
27910
|
/** Exports visible scene meshes as OBJ text, without texture images. */
|
|
27751
27911
|
toObj() {
|
|
27752
27912
|
return SceneExporter.toObj(this.scene);
|
|
@@ -29438,6 +29598,7 @@ export {
|
|
|
29438
29598
|
Ticker,
|
|
29439
29599
|
TintColors,
|
|
29440
29600
|
UVMapper,
|
|
29601
|
+
VideoExporter,
|
|
29441
29602
|
WrappedImage,
|
|
29442
29603
|
addBox3WireframeToObject,
|
|
29443
29604
|
addWireframeToMesh,
|