Skip to content

Commit efd3ae7

Browse files
authored
Merge pull request #181 from shadielhajj/ccapture
Offline recording capabilities (via CCapture). Sequel to PR #180.
2 parents 49278e6 + 2e582ee commit efd3ae7

8 files changed

+161
-10
lines changed

README.md

+26-8
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,27 @@ The extension also supports highlighting of compilation errors in the text edito
155155
![error example](https://raw.githubusercontent.com/stevensona/shader-toy/master/images/example3.png)
156156

157157
## Recording Capabilities
158-
The following settings allow to configure recording quality
158+
Two rendering modes are supported:
159+
* Realtime rendering: suitable for previews and scenarios where real-time interaction with the visuals are required. As with all real-time video capture, frame drops might occur, especially on lower spec hardware or when rendering very complex visuals.
160+
* Offline rendering: suitable for production-grade rendering. Each frame is rendered sequentially without any drops.
161+
162+
The default is real-time rendering to activate offline rendering set `shader-toy.recordOffline`to true.
163+
164+
### Shared parameters
165+
166+
* `shader-toy.recordTargetFramerate`: Set recording target frame-rate. Default is 30fps.
167+
* `shader-toy.recordMaxDuration`: Maximum recording duration in seconds. 0 (the default) will keep recording until the record button is pressed again.
168+
169+
### Real-time Rendering parameters
170+
159171
* `shader-toy.recordVideoContainer`: Set the video file container. Currently only `webm` is supported, but `mp4`support is coming [soon](https://chromestatus.com/feature/5163469011943424).
160172
* `shader-toy.recordVideoCodec`: Set video codec. `vp8`, `vp9`, `h264` and `avc1` are all supported. Default it `vp8`.
161173
* `shader-toy.recordVideoBitRate`: Set recording bit rate in bits/second. Default is 2500000.
162-
* `shader-toy.recordTargetFramerate`: Set recording target frame-rate. Default is 30fps.
163-
* `shader-toy.recordMaxDuration`: Maximum recording duration in seconds. 0 (the default) will keep recording until the record button is pressed again.
174+
175+
### Offline rendering parameters
176+
177+
* `shader-toy.recordOfflineFormat`: Offline recording format. Possible value are `webm`, `gif`, `png` and `jpg`. PNG and JPEG images are individual images packaged in a .tar archive.
178+
* `shader-toy.recordOfflineQuality`: Offline recording quality. 0 is lowest and 100 highest. Applies when format is `webm` or `jpg`.
164179

165180
## Requirements
166181

@@ -185,12 +200,15 @@ Contributions of any kind are welcome and encouraged.
185200
## Release Notes
186201

187202
### 0.11.4
188-
* Added `shader-toy.recordVideoContainer` (set video file container),
189-
* Added `shader-toy.recordVideoCodec` (set video codec),
190-
* Added `shader-toy.recordVideoBitRate` (set recording bit rate),
191-
* Added `shader-toy.recordMaxDuration` (set maximum recording duration),
192-
* Fixed the `shader-toy.recordTargetFramerate` setting,
203+
* Added `shader-toy.recordVideoContainer` (set video file container).
204+
* Added `shader-toy.recordVideoCodec` (set video codec).
205+
* Added `shader-toy.recordVideoBitRate` (set recording bit rate).
206+
* Added `shader-toy.recordMaxDuration` (set maximum recording duration).
207+
* Fixed the `shader-toy.recordTargetFramerate` setting.
193208
* Moved the Stats widget to the bottom left, so it doesn't overlap with the GUI.
209+
* Added `shader-toy.recordOffline` to switch between real-time and offline rendering.
210+
* Added `shader-toy.recordOfflineFormat`. Used in conjunction with `shader-toy.recordOffline`
211+
* Added `shader-toy.recordOfflineQuality`. Used in conjunction with `shader-toy.recordOffline`
194212

195213
### 0.11.3
196214
* Added option to reload on save,

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@
134134
"default": 0,
135135
"description": "Maximum recording duration in seconds. 0 (the default) will keep recording until the record button is pressed again."
136136
},
137+
"shader-toy.recordOffline": {
138+
"type": "boolean",
139+
"default": false,
140+
"description": "Enable high-quality offline recording."
141+
},
142+
"shader-toy.recordOfflineFormat": {
143+
"type": "string",
144+
"default": "webm",
145+
"description": "Offline recording quality. Possible value are webm, gif, png and jpg."
146+
},
147+
"shader-toy.recordOfflineQuality": {
148+
"type": "number",
149+
"default": 80,
150+
"description": "Offline recording format. 0 is lowest and 100 highest. Applies when format is webm or jpg."
151+
},
137152
"shader-toy.showPauseButton": {
138153
"type": "boolean",
139154
"default": true,

resources/CCapture.all.min.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/webview_base.html

+50-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<script src="<!-- Three.js -->"></script>
9393
<!-- Stats.js -->
9494
<!-- dat.gui -->
95+
<!-- CCapture.js -->
9596

9697
<canvas id="canvas"></canvas>
9798

@@ -236,6 +237,7 @@
236237
let frameCounter = 0;
237238
let recorder = null;
238239
let recordingTimeout = null;
240+
let offlineRecording = false;
239241

240242
// Audio Init
241243
// Audio Resume
@@ -396,6 +398,10 @@
396398
}
397399
}
398400

401+
if (offlineRecording) {
402+
recorder.capture(canvas);
403+
}
404+
399405
frameCounter++;
400406
}
401407
function forceAspectRatio(width, height) {
@@ -482,6 +488,48 @@
482488
}
483489
}
484490
function recordAction() {
491+
if (typeof CCapture !== 'undefined') {
492+
recordCCapture();
493+
} else {
494+
recordMediaRecorder();
495+
}
496+
}
497+
function recordCCapture() {
498+
let maxDuration = <!-- Record Max Duration -->;
499+
let targetFrameRate = <!-- Record Target Framerate -->;
500+
let format = <!-- Record Offline Format -->;
501+
let quality = <!-- Record Offline Quality -->;
502+
let extension = format == "png" || format == "jpg" ? "tar" : format;
503+
504+
let recordButton = document.getElementById("record");
505+
if (recorder == null) {
506+
recordButton.classList.add('recording');
507+
508+
offlineRecording = true;
509+
recorder = new CCapture( {
510+
format: format,
511+
quality: quality,
512+
timeLimit: maxDuration,
513+
framerate: targetFrameRate,
514+
} );
515+
recorder.start();
516+
}
517+
else {
518+
recordButton.classList.remove('recording');
519+
520+
recorder.stop();
521+
recorder.save((blob) => {
522+
let a = document.createElement('a');
523+
let url = URL.createObjectURL(blob);
524+
a.href = url;
525+
a.download = `shadertoy.${extension}`;
526+
a.click();
527+
} );
528+
recorder = null;
529+
offlineRecording = false;
530+
}
531+
}
532+
function recordMediaRecorder() {
485533
let recordButton = document.getElementById("record");
486534
if (recordingTimeout != null) {
487535
clearTimeout(recordingTimeout);
@@ -493,8 +541,9 @@
493541
let videoContainer = <!-- Record Video Container -->;
494542
let videoCodec = <!-- Record Video Codec -->;
495543
let maxDuration = <!-- Record Max Duration -->;
544+
let targetFrameRate = <!-- Record Target Framerate -->;
496545

497-
let stream = canvas.captureStream(<!-- Record Target Framerate -->);
546+
let stream = canvas.captureStream(targetFrameRate);
498547
let recorderOptions = {
499548
videoBitsPerSecond: <!-- Record Video Bit Rate -->,
500549
mimeType: `video/${videoContainer};codecs=${videoCodec}`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
import { WebviewExtension } from '../webview_extension';
4+
5+
export class CCaptureExtension implements WebviewExtension {
6+
private getWebviewResourcePath: (relativePath: string) => string;
7+
private generateStandalone: boolean;
8+
9+
constructor(getWebviewResourcePath: (relativePath: string) => string, generateStandalone: boolean) {
10+
this.getWebviewResourcePath = getWebviewResourcePath;
11+
this.generateStandalone = generateStandalone;
12+
}
13+
14+
public generateContent(): string {
15+
if (this.generateStandalone) {
16+
return `\
17+
<script src='https://cdn.jsdelivr.net/npm/[email protected]/build/CCapture.all.min.js'></script>`;
18+
}
19+
return `\
20+
<script src='${this.getWebviewResourcePath('CCapture.all.min.js')}'></script>`;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
import { WebviewExtension } from '../webview_extension';
4+
5+
export class RecordOfflineFormatExtension implements WebviewExtension {
6+
private recordOfflineFormat: string;
7+
8+
public constructor(recordOfflineFormat: string) {
9+
this.recordOfflineFormat = recordOfflineFormat;
10+
}
11+
12+
public generateContent(): string {
13+
return `"${this.recordOfflineFormat}"`;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
import { WebviewExtension } from '../webview_extension';
4+
5+
export class RecordOfflineQualityExtension implements WebviewExtension {
6+
private recordOfflineQuality: number;
7+
8+
public constructor(recordOfflineQuality: number) {
9+
this.recordOfflineQuality = recordOfflineQuality;
10+
}
11+
12+
public generateContent(): string {
13+
return `${this.recordOfflineQuality}`;
14+
}
15+
}

src/webviewcontentprovider.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { ThreeExtension } from './extensions/packages/three_extension';
2929
import { ThreeFlyControlsExtension } from './extensions/packages/three_flycontrols';
3030
import { StatsExtension } from './extensions/packages/stats_extension';
3131
import { DatGuiExtension } from './extensions/packages/dat_gui_extension';
32+
import { CCaptureExtension } from './extensions/packages/ccapture_extension';
3233

3334
import { PauseButtonStyleExtension } from './extensions/user_interface/pause_button_style_extension';
3435
import { PauseButtonExtension } from './extensions/user_interface/pause_button_extension';
@@ -71,6 +72,8 @@ import { RecordVideoContainerExtension } from './extensions/user_interface/recor
7172
import { RecordVideoCodecExtension } from './extensions/user_interface/record_video_codec_extension';
7273
import { RecordVideoBitRateExtension } from './extensions/user_interface/record_video_bit_rate_extension';
7374
import { RecordMaxDurationExtension } from './extensions/user_interface/record_max_duration_extension';
75+
import { RecordOfflineFormatExtension } from './extensions/user_interface/record_offline_format_extension';
76+
import { RecordOfflineQualityExtension } from './extensions/user_interface/record_offline_quality_extension';
7477

7578
export class WebviewContentProvider {
7679
private context: Context;
@@ -340,6 +343,10 @@ export class WebviewContentProvider {
340343
const statsExtension = new StatsExtension(getWebviewResourcePath, generateStandalone);
341344
this.webviewAssembler.addWebviewModule(statsExtension, '<!-- Stats.js -->');
342345
}
346+
if (this.context.getConfig<boolean>('recordOffline')) {
347+
const ccaptureExtension = new CCaptureExtension(getWebviewResourcePath, generateStandalone);
348+
this.webviewAssembler.addWebviewModule(ccaptureExtension, '<!-- CCapture.js -->');
349+
}
343350

344351
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
345352
// Pause Logic
@@ -390,7 +397,7 @@ export class WebviewContentProvider {
390397

391398
const recordTargetFramerate = this.context.getConfig<number>('recordTargetFramerate') || 30;
392399
const recordTargetFramerateExtension = new RecordTargetFramerateExtension(recordTargetFramerate);
393-
this.webviewAssembler.addReplaceModule(recordTargetFramerateExtension, 'let stream = canvas.captureStream(<!-- Record Target Framerate -->);', '<!-- Record Target Framerate -->');
400+
this.webviewAssembler.addReplaceModule(recordTargetFramerateExtension, 'let targetFrameRate = <!-- Record Target Framerate -->;', '<!-- Record Target Framerate -->');
394401

395402
const recordVideoContainer = this.context.getConfig<string>('recordVideoContainer') || "webm";
396403
const recordVideoContainerExtension = new RecordVideoContainerExtension(recordVideoContainer);
@@ -408,6 +415,14 @@ export class WebviewContentProvider {
408415
const recordMaxDurationExtension = new RecordMaxDurationExtension(recordMaxDuration);
409416
this.webviewAssembler.addReplaceModule(recordMaxDurationExtension, 'let maxDuration = <!-- Record Max Duration -->;', '<!-- Record Max Duration -->');
410417

418+
const recordOfflineFormat = this.context.getConfig<string>('recordOfflineFormat') || "webm";
419+
const recordOfflineFormatExtension = new RecordOfflineFormatExtension(recordOfflineFormat);
420+
this.webviewAssembler.addReplaceModule(recordOfflineFormatExtension, 'let format = <!-- Record Offline Format -->;', '<!-- Record Offline Format -->');
421+
422+
const recordOfflineQuality = this.context.getConfig<number>('recordOfflineQuality') || 80;
423+
const recordOfflineQualityExtension = new RecordOfflineQualityExtension(recordOfflineQuality);
424+
this.webviewAssembler.addReplaceModule(recordOfflineQualityExtension, 'let quality = <!-- Record Offline Quality -->;', '<!-- Record Offline Quality -->');
425+
411426
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
412427
// Reload Logic
413428
if (!generateStandalone) {

0 commit comments

Comments
 (0)