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/bundle.js
CHANGED
|
@@ -106580,6 +106580,7 @@ ${shader.fragmentShader}`.replace(
|
|
|
106580
106580
|
Ticker: () => Ticker,
|
|
106581
106581
|
TintColors: () => TintColors,
|
|
106582
106582
|
UVMapper: () => UVMapper,
|
|
106583
|
+
VideoExporter: () => VideoExporter,
|
|
106583
106584
|
WrappedImage: () => WrappedImage,
|
|
106584
106585
|
addBox3WireframeToObject: () => addBox3WireframeToObject,
|
|
106585
106586
|
addWireframeToMesh: () => addWireframeToMesh,
|
|
@@ -110099,6 +110100,134 @@ property list uchar int vertex_index
|
|
|
110099
110100
|
return new Texture().copy(copySource);
|
|
110100
110101
|
}
|
|
110101
110102
|
|
|
110103
|
+
// src/export/VideoExporter.ts
|
|
110104
|
+
init_dirname();
|
|
110105
|
+
init_buffer2();
|
|
110106
|
+
init_process2();
|
|
110107
|
+
var VideoExporter = class {
|
|
110108
|
+
constructor(source, options) {
|
|
110109
|
+
this.source = source;
|
|
110110
|
+
this.chunks = [];
|
|
110111
|
+
this.settled = false;
|
|
110112
|
+
this.stopping = false;
|
|
110113
|
+
this.onStart = () => {
|
|
110114
|
+
this.timer = setTimeout(() => {
|
|
110115
|
+
this.stopping = true;
|
|
110116
|
+
try {
|
|
110117
|
+
this.recorder.stop();
|
|
110118
|
+
} catch (error2) {
|
|
110119
|
+
this.cancel(error2);
|
|
110120
|
+
}
|
|
110121
|
+
}, this.duration * 1e3);
|
|
110122
|
+
};
|
|
110123
|
+
this.onData = (event) => {
|
|
110124
|
+
if (event.data.size) this.chunks.push(event.data);
|
|
110125
|
+
};
|
|
110126
|
+
this.onStop = () => {
|
|
110127
|
+
if (!this.stopping) return this.cancel(new Error("Video recording stopped before its duration elapsed"));
|
|
110128
|
+
if (!this.chunks.length) return this.cancel(new Error("The browser produced no video data"));
|
|
110129
|
+
const mimeType = this.chunks.find((chunk) => chunk.type)?.type || this.recorder.mimeType;
|
|
110130
|
+
const blob = new Blob(this.chunks, { type: mimeType });
|
|
110131
|
+
this.settled = true;
|
|
110132
|
+
this.cleanup();
|
|
110133
|
+
this.resolve(blob);
|
|
110134
|
+
};
|
|
110135
|
+
this.onError = (event) => {
|
|
110136
|
+
this.cancel(event.error ?? new Error("Video recording failed"));
|
|
110137
|
+
};
|
|
110138
|
+
this.onAbort = () => {
|
|
110139
|
+
this.cancel(this.signal.reason);
|
|
110140
|
+
};
|
|
110141
|
+
this.onTrackEnded = () => {
|
|
110142
|
+
this.cancel(new Error("The video capture stream stopped producing frames"));
|
|
110143
|
+
};
|
|
110144
|
+
const fps = options.fps ?? 30;
|
|
110145
|
+
if (!Number.isFinite(options.duration) || options.duration <= 0 || options.duration * 1e3 > 2147483647) {
|
|
110146
|
+
throw new RangeError("Video duration must be greater than zero and at most 2147483.647 seconds");
|
|
110147
|
+
}
|
|
110148
|
+
if (!Number.isFinite(fps) || fps <= 0) throw new RangeError("Video fps must be a finite positive number");
|
|
110149
|
+
const bitrate = options.videoBitsPerSecond;
|
|
110150
|
+
if (bitrate !== void 0 && (!Number.isInteger(bitrate) || bitrate <= 0 || bitrate > 4294967295)) {
|
|
110151
|
+
throw new RangeError("Video bitrate must be a positive unsigned 32-bit integer");
|
|
110152
|
+
}
|
|
110153
|
+
if (!source.width || !source.height) throw new Error("Cannot export video from an empty canvas");
|
|
110154
|
+
options.signal?.throwIfAborted();
|
|
110155
|
+
if (typeof document === "undefined" || typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
|
|
110156
|
+
throw new Error("Video export requires a browser with MediaRecorder support");
|
|
110157
|
+
}
|
|
110158
|
+
const mimeTypes2 = options.mimeType === void 0 ? ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"] : [options.mimeType];
|
|
110159
|
+
const mimeType = mimeTypes2.find((type) => type.startsWith("video/") && MediaRecorder.isTypeSupported(type));
|
|
110160
|
+
if (!mimeType) throw new Error("The browser does not support the requested video format");
|
|
110161
|
+
this.canvas = document.createElement("canvas");
|
|
110162
|
+
this.canvas.width = source.width;
|
|
110163
|
+
this.canvas.height = source.height;
|
|
110164
|
+
if (typeof this.canvas.captureStream !== "function") throw new Error("Video export requires canvas captureStream support");
|
|
110165
|
+
const context = this.canvas.getContext("2d", { alpha: false });
|
|
110166
|
+
if (!context) throw new Error("Could not create a canvas for video export");
|
|
110167
|
+
this.context = context;
|
|
110168
|
+
this.signal = options.signal;
|
|
110169
|
+
this.duration = options.duration;
|
|
110170
|
+
this.result = new Promise((resolve, reject) => {
|
|
110171
|
+
this.resolve = resolve;
|
|
110172
|
+
this.reject = reject;
|
|
110173
|
+
});
|
|
110174
|
+
try {
|
|
110175
|
+
this.stream = this.canvas.captureStream(fps);
|
|
110176
|
+
this.recorder = new MediaRecorder(this.stream, { mimeType, videoBitsPerSecond: bitrate });
|
|
110177
|
+
this.recorder.addEventListener("start", this.onStart);
|
|
110178
|
+
this.recorder.addEventListener("dataavailable", this.onData);
|
|
110179
|
+
this.recorder.addEventListener("stop", this.onStop);
|
|
110180
|
+
this.recorder.addEventListener("error", this.onError);
|
|
110181
|
+
for (const track of this.stream.getTracks()) {
|
|
110182
|
+
track.addEventListener("ended", this.onTrackEnded);
|
|
110183
|
+
}
|
|
110184
|
+
this.signal?.addEventListener("abort", this.onAbort, { once: true });
|
|
110185
|
+
this.recorder.start();
|
|
110186
|
+
} catch (error2) {
|
|
110187
|
+
this.cancel(error2);
|
|
110188
|
+
}
|
|
110189
|
+
}
|
|
110190
|
+
/** Copies a freshly drawn source frame before WebGL can clear its drawing buffer. */
|
|
110191
|
+
capture() {
|
|
110192
|
+
if (this.settled || this.stopping) return;
|
|
110193
|
+
try {
|
|
110194
|
+
this.context.fillStyle = "#000000";
|
|
110195
|
+
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
110196
|
+
this.context.drawImage(this.source, 0, 0, this.canvas.width, this.canvas.height);
|
|
110197
|
+
} catch (error2) {
|
|
110198
|
+
this.cancel(error2);
|
|
110199
|
+
}
|
|
110200
|
+
}
|
|
110201
|
+
/** Cancels recording, releases its capture stream, and rejects {@link result}. */
|
|
110202
|
+
cancel(reason = new DOMException("Video export cancelled", "AbortError")) {
|
|
110203
|
+
if (this.settled) return;
|
|
110204
|
+
this.settled = true;
|
|
110205
|
+
this.cleanup();
|
|
110206
|
+
this.reject(reason);
|
|
110207
|
+
}
|
|
110208
|
+
cleanup() {
|
|
110209
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
110210
|
+
this.signal?.removeEventListener("abort", this.onAbort);
|
|
110211
|
+
if (this.recorder) {
|
|
110212
|
+
this.recorder.removeEventListener("start", this.onStart);
|
|
110213
|
+
this.recorder.removeEventListener("dataavailable", this.onData);
|
|
110214
|
+
this.recorder.removeEventListener("stop", this.onStop);
|
|
110215
|
+
this.recorder.removeEventListener("error", this.onError);
|
|
110216
|
+
if (this.recorder.state !== "inactive") {
|
|
110217
|
+
try {
|
|
110218
|
+
this.recorder.stop();
|
|
110219
|
+
} catch {
|
|
110220
|
+
}
|
|
110221
|
+
}
|
|
110222
|
+
}
|
|
110223
|
+
for (const track of this.stream?.getTracks() ?? []) {
|
|
110224
|
+
track.removeEventListener("ended", this.onTrackEnded);
|
|
110225
|
+
track.stop();
|
|
110226
|
+
}
|
|
110227
|
+
this.chunks.length = 0;
|
|
110228
|
+
}
|
|
110229
|
+
};
|
|
110230
|
+
|
|
110102
110231
|
// src/gui/index.ts
|
|
110103
110232
|
init_dirname();
|
|
110104
110233
|
init_buffer2();
|
|
@@ -117544,6 +117673,9 @@ gl_FragDepth=readDepth(vUv);
|
|
|
117544
117673
|
this._inAnimationLoop = true;
|
|
117545
117674
|
try {
|
|
117546
117675
|
this.animate(time);
|
|
117676
|
+
} catch (error2) {
|
|
117677
|
+
this._videoExporter?.cancel(error2);
|
|
117678
|
+
throw error2;
|
|
117547
117679
|
} finally {
|
|
117548
117680
|
this._inAnimationLoop = false;
|
|
117549
117681
|
}
|
|
@@ -117738,16 +117870,18 @@ gl_FragDepth=readDepth(vUv);
|
|
|
117738
117870
|
if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
|
|
117739
117871
|
};
|
|
117740
117872
|
}
|
|
117741
|
-
/** Starts or resumes the animation loop and registered frame callbacks. */
|
|
117873
|
+
/** Starts or resumes the animation loop and registered frame callbacks. Has no effect while running. */
|
|
117742
117874
|
start() {
|
|
117743
|
-
if (this._disposed) return;
|
|
117875
|
+
if (this._disposed || this._running) return;
|
|
117744
117876
|
this.stop();
|
|
117745
117877
|
this._running = true;
|
|
117746
117878
|
if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
|
|
117747
117879
|
}
|
|
117748
|
-
/** Pauses rendering and frame callbacks. Call {@link start} to resume. */
|
|
117880
|
+
/** Pauses rendering and frame callbacks, and cancels video export. Call {@link start} to resume. */
|
|
117749
117881
|
stop() {
|
|
117750
117882
|
if (this._disposed) return;
|
|
117883
|
+
this._videoExporter?.cancel();
|
|
117884
|
+
this._videoExporter = void 0;
|
|
117751
117885
|
this._running = false;
|
|
117752
117886
|
if (this._inAnimationLoop) {
|
|
117753
117887
|
queueMicrotask(() => {
|
|
@@ -117809,7 +117943,7 @@ gl_FragDepth=readDepth(vUv);
|
|
|
117809
117943
|
this._controls.update();
|
|
117810
117944
|
}
|
|
117811
117945
|
if (!this._running) return;
|
|
117812
|
-
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size) return;
|
|
117946
|
+
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
|
|
117813
117947
|
const interval = this._frameInterval;
|
|
117814
117948
|
if (interval) {
|
|
117815
117949
|
const next = this._nextFrameTime;
|
|
@@ -117834,6 +117968,7 @@ gl_FragDepth=readDepth(vUv);
|
|
|
117834
117968
|
} else {
|
|
117835
117969
|
this.renderer.render(this.scene, this.camera);
|
|
117836
117970
|
}
|
|
117971
|
+
this._videoExporter?.capture();
|
|
117837
117972
|
this.dirty = false;
|
|
117838
117973
|
if (this._stats) {
|
|
117839
117974
|
this._stats.end();
|
|
@@ -117854,6 +117989,35 @@ gl_FragDepth=readDepth(vUv);
|
|
|
117854
117989
|
const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
|
|
117855
117990
|
return canvas.toDataURL(mime, quality);
|
|
117856
117991
|
}
|
|
117992
|
+
/**
|
|
117993
|
+
* Records a silent browser video in real time, including frame callbacks and animated textures.
|
|
117994
|
+
* Keep the tab visible; browser scheduling and `render.fpsLimit` limit the capture rate.
|
|
117995
|
+
* The video keeps the initial drawing-buffer size and fills transparent pixels with black.
|
|
117996
|
+
* Temporarily starts a stopped renderer. Stop, disposal, or `options.signal` cancels the export.
|
|
117997
|
+
* Only one export can run at a time. Use the returned Blob's type to select a file extension.
|
|
117998
|
+
*/
|
|
117999
|
+
async toVideo(options) {
|
|
118000
|
+
if (this._disposed) throw new Error("Cannot export a video from a disposed renderer");
|
|
118001
|
+
if (this._videoExporter) throw new Error("A video export is already running");
|
|
118002
|
+
const exporter = new VideoExporter(this.renderer.domElement, options);
|
|
118003
|
+
const wasRunning = this._running;
|
|
118004
|
+
try {
|
|
118005
|
+
try {
|
|
118006
|
+
if (!wasRunning) this.start();
|
|
118007
|
+
this._videoExporter = exporter;
|
|
118008
|
+
if (this._controls?.enabled) this._controls.update();
|
|
118009
|
+
this.drawFrame();
|
|
118010
|
+
} catch (error2) {
|
|
118011
|
+
exporter.cancel(error2);
|
|
118012
|
+
}
|
|
118013
|
+
return await exporter.result;
|
|
118014
|
+
} finally {
|
|
118015
|
+
if (this._videoExporter === exporter) {
|
|
118016
|
+
this._videoExporter = void 0;
|
|
118017
|
+
if (!wasRunning) this.stop();
|
|
118018
|
+
}
|
|
118019
|
+
}
|
|
118020
|
+
}
|
|
117857
118021
|
/** Exports visible scene meshes as OBJ text, without texture images. */
|
|
117858
118022
|
toObj() {
|
|
117859
118023
|
return SceneExporter.toObj(this.scene);
|