fragment-tools 0.1.13 → 0.1.15

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.
Files changed (184) hide show
  1. package/.prettierignore +1 -2
  2. package/.prettierrc +23 -7
  3. package/README.md +28 -9
  4. package/bin/index.js +70 -10
  5. package/package.json +14 -6
  6. package/src/cli/build.js +125 -0
  7. package/src/cli/create.js +238 -0
  8. package/src/cli/createConfig.js +82 -0
  9. package/src/cli/createFragmentFile.js +70 -0
  10. package/src/cli/getEntries.js +85 -0
  11. package/src/cli/log.js +36 -24
  12. package/src/cli/plugins/check-dependencies.js +88 -42
  13. package/src/cli/plugins/hot-shader-replacement.js +408 -0
  14. package/src/cli/plugins/hot-sketch-reload.js +21 -25
  15. package/src/cli/plugins/save.js +101 -0
  16. package/src/cli/preview.js +55 -0
  17. package/src/cli/prompts.js +260 -0
  18. package/src/cli/run.js +131 -0
  19. package/src/cli/templates/blank/index.js +33 -0
  20. package/src/cli/templates/blank/meta.json +4 -0
  21. package/src/cli/templates/default/index.js +39 -0
  22. package/src/cli/templates/default/meta.json +5 -0
  23. package/src/cli/templates/fragment-gl/index.js +37 -0
  24. package/src/cli/templates/fragment-gl/meta.json +4 -0
  25. package/src/cli/templates/p5/index.js +32 -0
  26. package/src/cli/templates/p5/meta.json +5 -0
  27. package/src/cli/templates/p5-webgl/fragment.fs +14 -0
  28. package/src/cli/templates/p5-webgl/index.js +67 -0
  29. package/src/cli/templates/p5-webgl/meta.json +5 -0
  30. package/src/cli/templates/three-fragment/fragment.fs +10 -0
  31. package/src/cli/templates/three-fragment/index.js +95 -0
  32. package/src/cli/templates/three-fragment/meta.json +5 -0
  33. package/src/cli/templates/three-orthographic/index.js +55 -0
  34. package/src/cli/templates/three-orthographic/meta.json +5 -0
  35. package/src/cli/templates/three-perspective/index.js +52 -0
  36. package/src/cli/templates/three-perspective/meta.json +5 -0
  37. package/src/cli/utils.js +70 -0
  38. package/src/cli/ws.js +87 -78
  39. package/src/client/app/App.svelte +3 -3
  40. package/src/client/app/client.js +55 -39
  41. package/src/client/app/components/IconCross.svelte +18 -18
  42. package/src/client/app/components/Init.svelte +40 -8
  43. package/src/client/app/components/KeyBinding.svelte +22 -22
  44. package/src/client/app/helpers.js +42 -0
  45. package/src/client/app/hooks.js +20 -0
  46. package/src/client/app/inputs/Input.js +9 -9
  47. package/src/client/app/inputs/Keyboard.js +13 -15
  48. package/src/client/app/inputs/MIDI.js +14 -15
  49. package/src/client/app/inputs/Mouse.js +1 -1
  50. package/src/client/app/inputs/Webcam.js +89 -88
  51. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
  52. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +7 -6
  53. package/src/client/app/lib/canvas-recorder/H264Recorder.js +45 -0
  54. package/src/client/app/lib/canvas-recorder/MP4Recorder.js +7 -9
  55. package/src/client/app/lib/canvas-recorder/WebMRecorder.js +3 -4
  56. package/src/client/app/lib/canvas-recorder/mp4.js +1649 -15
  57. package/src/client/app/lib/canvas-recorder/utils.js +33 -17
  58. package/src/client/app/lib/gl/Geometry.js +11 -8
  59. package/src/client/app/lib/gl/Program.js +38 -19
  60. package/src/client/app/lib/gl/Renderer.js +163 -156
  61. package/src/client/app/lib/gl/Texture.js +113 -85
  62. package/src/client/app/lib/gl/index.js +12 -12
  63. package/src/client/app/lib/gl/utils.js +1 -3
  64. package/src/client/app/lib/helpers/frameDebounce.js +30 -30
  65. package/src/client/app/lib/loader/index.js +10 -10
  66. package/src/client/app/lib/loader/loadImage.js +15 -15
  67. package/src/client/app/lib/loader/loadScript.js +1 -1
  68. package/src/client/app/lib/paper-sizes.js +75 -76
  69. package/src/client/app/lib/presets.js +25 -5
  70. package/src/client/app/lib/tempo/Analyser.js +18 -17
  71. package/src/client/app/lib/tempo/Range.js +15 -12
  72. package/src/client/app/lib/tempo/index.js +34 -27
  73. package/src/client/app/modules/AudioAnalyser/Range.svelte +69 -72
  74. package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +20 -19
  75. package/src/client/app/modules/AudioAnalyser.svelte +52 -35
  76. package/src/client/app/modules/Console/ConsoleLine.svelte +193 -172
  77. package/src/client/app/modules/Console.svelte +76 -74
  78. package/src/client/app/modules/Exports.svelte +62 -43
  79. package/src/client/app/modules/MidiPanel.svelte +100 -101
  80. package/src/client/app/modules/Monitor.svelte +57 -57
  81. package/src/client/app/modules/Params.svelte +128 -103
  82. package/src/client/app/renderers/2DRenderer.js +3 -3
  83. package/src/client/app/renderers/FragmentRenderer.js +30 -23
  84. package/src/client/app/renderers/P5GLRenderer.js +144 -0
  85. package/src/client/app/renderers/P5Renderer.js +10 -7
  86. package/src/client/app/renderers/THREERenderer.js +136 -94
  87. package/src/client/app/stores/audioAnalysis.js +3 -4
  88. package/src/client/app/stores/console.js +9 -10
  89. package/src/client/app/stores/errors.js +1 -1
  90. package/src/client/app/stores/exports.js +36 -20
  91. package/src/client/app/stores/index.js +2 -2
  92. package/src/client/app/stores/layout.js +143 -138
  93. package/src/client/app/stores/multisampling.js +4 -4
  94. package/src/client/app/stores/props.js +76 -13
  95. package/src/client/app/stores/renderers.js +26 -15
  96. package/src/client/app/stores/rendering.js +108 -89
  97. package/src/client/app/stores/sketches.js +7 -9
  98. package/src/client/app/stores/time.js +18 -18
  99. package/src/client/app/stores/utils.js +95 -38
  100. package/src/client/app/transitions/fade.js +3 -3
  101. package/src/client/app/transitions/index.js +6 -7
  102. package/src/client/app/transitions/splitX.js +2 -2
  103. package/src/client/app/transitions/splitY.js +2 -2
  104. package/src/client/app/triggers/Keyboard.js +88 -79
  105. package/src/client/app/triggers/MIDI.js +110 -84
  106. package/src/client/app/triggers/Mouse.js +73 -65
  107. package/src/client/app/triggers/Trigger.js +59 -58
  108. package/src/client/app/triggers/index.js +7 -7
  109. package/src/client/app/triggers/shared.js +5 -5
  110. package/src/client/app/ui/Build.svelte +70 -71
  111. package/src/client/app/ui/ErrorOverlay.svelte +118 -104
  112. package/src/client/app/ui/Field.svelte +393 -258
  113. package/src/client/app/ui/FieldGroup.svelte +106 -94
  114. package/src/client/app/ui/FieldSection.svelte +127 -116
  115. package/src/client/app/ui/FieldSpace.svelte +29 -30
  116. package/src/client/app/ui/FieldTrigger.svelte +256 -244
  117. package/src/client/app/ui/FieldTriggers.svelte +46 -46
  118. package/src/client/app/ui/FloatingParams.svelte +29 -30
  119. package/src/client/app/ui/Layout.svelte +31 -32
  120. package/src/client/app/ui/LayoutColumn.svelte +4 -4
  121. package/src/client/app/ui/LayoutComponent.svelte +239 -225
  122. package/src/client/app/ui/LayoutResizer.svelte +195 -176
  123. package/src/client/app/ui/LayoutRoot.svelte +6 -6
  124. package/src/client/app/ui/LayoutRow.svelte +4 -4
  125. package/src/client/app/ui/LayoutToolbar.svelte +191 -194
  126. package/src/client/app/ui/Module.svelte +134 -135
  127. package/src/client/app/ui/ModuleHeaderAction.svelte +81 -78
  128. package/src/client/app/ui/ModuleHeaderButton.svelte +12 -12
  129. package/src/client/app/ui/ModuleHeaderSelect.svelte +47 -37
  130. package/src/client/app/ui/ModuleRenderer.svelte +26 -27
  131. package/src/client/app/ui/OutputRenderer.svelte +112 -105
  132. package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
  133. package/src/client/app/ui/ParamsOutput.svelte +130 -113
  134. package/src/client/app/ui/Preview.svelte +7 -8
  135. package/src/client/app/ui/SelectChevrons.svelte +27 -15
  136. package/src/client/app/ui/SketchRenderer.svelte +780 -667
  137. package/src/client/app/ui/SketchSelect.svelte +50 -44
  138. package/src/client/app/ui/fields/ButtonInput.svelte +61 -48
  139. package/src/client/app/ui/fields/CheckboxInput.svelte +67 -61
  140. package/src/client/app/ui/fields/ColorInput.svelte +294 -238
  141. package/src/client/app/ui/fields/FieldInputRow.svelte +8 -8
  142. package/src/client/app/ui/fields/ImageInput.svelte +123 -121
  143. package/src/client/app/ui/fields/Input.svelte +100 -111
  144. package/src/client/app/ui/fields/IntervalInput.svelte +268 -0
  145. package/src/client/app/ui/fields/ListInput.svelte +96 -96
  146. package/src/client/app/ui/fields/NumberInput.svelte +120 -116
  147. package/src/client/app/ui/fields/ProgressInput.svelte +99 -73
  148. package/src/client/app/ui/fields/Select.svelte +137 -124
  149. package/src/client/app/ui/fields/TextInput.svelte +10 -11
  150. package/src/client/app/ui/fields/VectorInput.svelte +86 -82
  151. package/src/client/app/utils/canvas.utils.js +189 -208
  152. package/src/client/app/utils/color.utils.js +138 -101
  153. package/src/client/app/utils/fields.utils.js +131 -0
  154. package/src/client/app/utils/file.utils.js +209 -37
  155. package/src/client/app/utils/glsl.utils.js +2 -2
  156. package/src/client/app/utils/glslErrors.js +49 -31
  157. package/src/client/app/utils/index.js +32 -29
  158. package/src/client/app/utils/math.utils.js +14 -10
  159. package/src/client/index.html +16 -16
  160. package/src/client/main.js +4 -4
  161. package/src/client/public/css/global.css +26 -16
  162. package/src/cli/db.js +0 -17
  163. package/src/cli/index.js +0 -198
  164. package/src/cli/plugins/db.js +0 -12
  165. package/src/cli/plugins/hot-shader-reload.js +0 -86
  166. package/src/cli/plugins/screenshot.js +0 -46
  167. package/src/cli/server.js +0 -153
  168. package/src/cli/templates/2d.js +0 -15
  169. package/src/cli/templates/blank.js +0 -13
  170. package/src/cli/templates/fragment.js +0 -18
  171. package/src/cli/templates/index.js +0 -27
  172. package/src/cli/templates/p5.js +0 -13
  173. package/src/cli/templates/three-fragment.js +0 -53
  174. package/src/cli/templates/three-orthographic.js +0 -23
  175. package/src/cli/templates/three-perspective.js +0 -20
  176. package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +0 -56
  177. package/src/client/app/utils/props.utils.js +0 -51
  178. package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
  179. package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
  180. package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
  181. package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
  182. package/src/client/public/js/ffmpeg.min.js +0 -2
  183. package/src/client/public/js/ffmpeg.min.js.map +0 -1
  184. /package/src/cli/templates/{fragment.fs → fragment-gl/fragment.fs} +0 -0
@@ -1,13 +1,45 @@
1
1
  <script>
2
- import { assignSketchFiles } from "../triggers/shared.js";
3
- import { sketchesKeys } from "../stores/sketches.js";
4
- import "../utils/glslErrors.js";
2
+ import { assignSketchFiles } from '../triggers/shared.js';
3
+ import { loadAll, sketchesKeys, sketches } from '../stores/sketches.js';
4
+ import { onSketchReload } from '@fragment/sketches';
5
+ import { getFilename } from '../utils/file.utils.js';
6
+ import '../utils/glslErrors.js';
7
+ import { props, reconcile } from '../stores/props.js';
5
8
 
6
- sketchesKeys.subscribe((keys) => {
7
- if (keys.length > 0) {
8
- assignSketchFiles(keys);
9
- }
10
- })
9
+ sketches.subscribe((sketches) => {
10
+ props.update((currentProps) => {
11
+ Object.keys(sketches).forEach((key) => {
12
+ const sketch = sketches[key];
11
13
 
14
+ if (sketch) {
15
+ // sketch can be undefined if failed to load
16
+ currentProps[key] = reconcile(
17
+ sketch.props,
18
+ currentProps[key],
19
+ );
20
+ }
21
+ });
12
22
 
23
+ return currentProps;
24
+ });
25
+ });
26
+
27
+ sketchesKeys.subscribe((keys) => {
28
+ if (keys.length > 0) {
29
+ assignSketchFiles(keys);
30
+ }
31
+ });
32
+
33
+ onSketchReload(({ sketches }) => {
34
+ loadAll(sketches);
35
+ });
36
+
37
+ $: prefix =
38
+ $sketchesKeys.length === 1 ? `${getFilename($sketchesKeys[0])} | ` : '';
39
+
40
+ $: title = `${prefix}fragment`;
13
41
  </script>
42
+
43
+ <svelte:head>
44
+ <title>{title}</title>
45
+ </svelte:head>
@@ -1,32 +1,32 @@
1
1
  <script>
2
- import { createEventDispatcher, onDestroy, onMount } from "svelte";
3
- import { onKeyPress, onKeyDown, onKeyUp } from "../triggers";
2
+ import { createEventDispatcher, onDestroy, onMount } from 'svelte';
3
+ import { onKeyPress, onKeyDown, onKeyUp } from '../triggers';
4
4
 
5
- export let key;
6
- export let type = "press";
5
+ export let key;
6
+ export let type = 'press';
7
7
 
8
- const dispatch = createEventDispatcher();
8
+ const dispatch = createEventDispatcher();
9
9
 
10
- const triggers = {
11
- "press": onKeyPress,
12
- "down": onKeyDown,
13
- "up": onKeyUp,
14
- };
10
+ const triggers = {
11
+ press: onKeyPress,
12
+ down: onKeyDown,
13
+ up: onKeyUp,
14
+ };
15
15
 
16
- const triggerType = triggers[type];
16
+ const triggerType = triggers[type];
17
17
 
18
- let trigger;
18
+ let trigger;
19
19
 
20
- onMount(() => {
21
- trigger = triggerType(key, (event) => {
22
- dispatch('trigger', event);
20
+ onMount(() => {
21
+ trigger = triggerType(key, (event) => {
22
+ dispatch('trigger', event);
23
+ });
23
24
  });
24
- })
25
25
 
26
- onDestroy(() => {
27
- if (trigger) {
28
- trigger.destroy();
29
- trigger = null;
30
- }
31
- });
26
+ onDestroy(() => {
27
+ if (trigger) {
28
+ trigger.destroy();
29
+ trigger = null;
30
+ }
31
+ });
32
32
  </script>
@@ -0,0 +1,42 @@
1
+ import { props } from './stores/props';
2
+
3
+ const propHandler = {
4
+ set: function (target, key, value, receiver) {
5
+ Reflect.set(target, key, value, receiver);
6
+
7
+ if (key === 'value') {
8
+ props.update((currentProps) => currentProps);
9
+ }
10
+
11
+ return true;
12
+ },
13
+ };
14
+
15
+ const propsHandler = {
16
+ get: (target, key) => {
17
+ if (typeof target[key] === 'object' && target[key] !== null) {
18
+ return new Proxy(target[key], propHandler);
19
+ }
20
+ },
21
+ set: (target, key, value) => {
22
+ console.log('new set', target, key, value);
23
+
24
+ target[key] = value;
25
+
26
+ props.update((currentProps) => currentProps);
27
+
28
+ return true;
29
+ },
30
+ deleteProperty: (target, prop) => {
31
+ if (prop in target) {
32
+ delete target[prop];
33
+ props.update((currentProps) => currentProps);
34
+
35
+ return true;
36
+ }
37
+ },
38
+ };
39
+
40
+ export function reactiveProps(props = {}) {
41
+ return new Proxy(props, propsHandler);
42
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ removeBeforeCaptureFrom,
3
+ removeAfterCaptureFrom,
4
+ removeBeforeRecordFrom,
5
+ removeAfterRecordFrom,
6
+ } from './stores/exports';
7
+
8
+ export {
9
+ onBeforeCapture,
10
+ onAfterCapture,
11
+ onBeforeRecord,
12
+ onAfterRecord,
13
+ } from './stores/exports';
14
+
15
+ export function removeHooksFrom(context) {
16
+ removeBeforeCaptureFrom(context);
17
+ removeAfterCaptureFrom(context);
18
+ removeBeforeRecordFrom(context);
19
+ removeAfterRecordFrom(context);
20
+ }
@@ -1,15 +1,15 @@
1
1
  class Input {
2
- constructor() {
3
- this.enabled = true;
4
- }
2
+ constructor() {
3
+ this.enabled = true;
4
+ }
5
5
 
6
- enable() {
7
- this.enabled = true;
8
- }
6
+ enable() {
7
+ this.enabled = true;
8
+ }
9
9
 
10
- disable() {
11
- this.enabled = false;
12
- }
10
+ disable() {
11
+ this.enabled = false;
12
+ }
13
13
  }
14
14
 
15
15
  export default Input;
@@ -1,21 +1,19 @@
1
- import Input from "./Input";
1
+ import Input from './Input';
2
2
 
3
3
  class Keyboard extends Input {
4
+ /**
5
+ *
6
+ * @param {KeyboardEvent} event
7
+ */
8
+ getStepFromEvent(event) {
9
+ if (event.shiftKey) {
10
+ return 10;
11
+ } else if (event.altKey) {
12
+ return 0.1;
13
+ }
4
14
 
5
- /**
6
- *
7
- * @param {KeyboardEvent} event
8
- */
9
- getStepFromEvent(event) {
10
- if (event.shiftKey) {
11
- return 10;
12
- } else if (event.altKey) {
13
- return 0.1;
14
- }
15
-
16
- return 1;
17
- }
18
-
15
+ return 1;
16
+ }
19
17
  }
20
18
 
21
19
  export default new Keyboard();
@@ -1,17 +1,16 @@
1
- import Input from "./Input";
1
+ import Input from './Input';
2
2
 
3
3
  const commands = {
4
- 0x8: "noteoff",
5
- 0x9: "noteon",
6
- 0xB: "controlchange",
4
+ 0x8: 'noteoff',
5
+ 0x9: 'noteon',
6
+ 0xb: 'controlchange',
7
7
  };
8
8
 
9
- const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
9
+ const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
10
10
 
11
- const LOCAL_STORAGE_KEY = "midi.requested";
11
+ const LOCAL_STORAGE_KEY = 'midi.requested';
12
12
 
13
13
  class MIDI extends Input {
14
-
15
14
  constructor() {
16
15
  super();
17
16
 
@@ -42,25 +41,25 @@ class MIDI extends Input {
42
41
 
43
42
  if (this.inputs.size === 1) {
44
43
  const [entry] = this.inputs.values();
45
- const { id } = entry;
44
+ const { id } = entry;
46
45
 
47
46
  this.selectedInputID = id;
48
47
  }
49
48
 
50
49
  if (this.outputs.size === 1) {
51
50
  const [entry] = this.outputs.values();
52
- const { id } = entry;
51
+ const { id } = entry;
53
52
 
54
53
  this.selectedOutputID = id;
55
54
  }
56
55
  }
57
56
 
58
57
  attachListeners() {
59
- this.inputs.forEach(entry => {
58
+ this.inputs.forEach((entry) => {
60
59
  entry.onmidimessage = (event) => {
61
60
  this.onMessage(event);
62
61
  };
63
- })
62
+ });
64
63
  }
65
64
 
66
65
  addEventListener(eventName, fn) {
@@ -76,14 +75,14 @@ class MIDI extends Input {
76
75
  let type = commands[command];
77
76
 
78
77
  let channel = (event.data[0] & 0xf) + 1;
79
- let data1 = event.data[1]
78
+ let data1 = event.data[1];
80
79
  let data2 = event.data[2];
81
80
 
82
81
  let note = {
83
82
  number: data1,
84
83
  name: notes[data1 % 12],
85
84
  };
86
-
85
+
87
86
  let velocity = data2 / 127;
88
87
  let rawVelocity = data2;
89
88
 
@@ -117,7 +116,7 @@ class MIDI extends Input {
117
116
  if (this.listeners.has(eventName)) {
118
117
  const listeners = this.listeners.get(eventName);
119
118
 
120
- listeners.forEach(listener => listener(data));
119
+ listeners.forEach((listener) => listener(data));
121
120
  }
122
121
  }
123
122
 
@@ -135,7 +134,7 @@ class MIDI extends Input {
135
134
  this.requesting = false;
136
135
  this.start();
137
136
  }
138
- } catch(error) {
137
+ } catch (error) {
139
138
  this.handleError(error);
140
139
  }
141
140
  }
@@ -1,4 +1,4 @@
1
- import Input from "./Input";
1
+ import Input from './Input';
2
2
 
3
3
  class Mouse extends Input {}
4
4
 
@@ -1,98 +1,99 @@
1
- import Input from "./Input";
1
+ import Input from './Input';
2
2
 
3
3
  class Webcam extends Input {
4
+ constructor() {
5
+ super();
4
6
 
5
- constructor() {
6
- super();
7
+ this._useCanvas = false;
8
+ this.stream = null;
9
+ this.requested = false;
7
10
 
8
- this._useCanvas = false;
9
- this.stream = null;
10
- this.requested = false;
11
+ this.video = document.createElement('video');
12
+ this.canvas = document.createElement('canvas');
13
+ this.context = null;
14
+ }
11
15
 
12
- this.video = document.createElement('video');
13
- this.canvas = document.createElement('canvas');
14
- this.context = null;
15
- }
16
-
17
- /**
16
+ /**
18
17
  @param {Boolean} value
19
18
  */
20
- set useCanvas(value) {
21
- const prev = this.useCanvas;
22
-
23
- this._useCanvas = value;
24
-
25
- if (!prev && this._useCanvas) {
26
- this.prepareCanvas();
27
- } else if (prev && this._useCanvas) {
28
- cancelAnimationFrame(this._raf);
29
- this._raf = null;
30
- }
31
- }
32
-
33
- get useCanvas() {
34
- return this._useCanvas;
35
- }
36
-
37
- enable() {
38
- if (!this.requested) {
39
- this.request();
40
- }
41
- }
42
-
43
- disable() {
44
- this.enabled = false;
45
-
46
- if (this.stream) {
47
- if (this.useCanvas && this._raf) {
48
- cancelAnimationFrame(this._raf);
49
- this._raf = null;
50
- }
51
-
52
- this.stream.getTracks().forEach(track => {
53
- track.stop()
54
- });
55
-
56
- this.stream = null;
57
- }
58
- }
59
-
60
- prepareCanvas() {
61
- this.canvas.width = video.videoWidth;
62
- this.canvas.height = video.videoHeight;
63
-
64
- this.context = this.canvas.getContext("2d");
65
- }
66
-
67
- drawToCanvas() {
68
- this.context.drawImage(video, 0, 0, canvas.width, canvas.height);
69
-
70
- this._raf = requestAnimationFrame(() => {
71
- this.drawToCanvas()
72
- });
73
- }
74
-
75
- async request() {
76
- try {
77
- this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
78
- this.requested = true;
79
- this.enabled = true;
80
-
81
- this.video.addEventListener('canplay', () => {
82
- this.prepareCanvas();
83
-
84
- if (this.useCanvas) {
85
- this.drawToCanvas();
86
- }
87
- });
88
- this.video.srcObject = this.stream;
89
- this.video.play();
90
- } catch(error) {
91
- console.error(`Error while trying to access webcam.`);
92
- console.log(error);
93
- }
94
- }
95
-
19
+ set useCanvas(value) {
20
+ const prev = this.useCanvas;
21
+
22
+ this._useCanvas = value;
23
+
24
+ if (!prev && this._useCanvas) {
25
+ this.prepareCanvas();
26
+ } else if (prev && this._useCanvas) {
27
+ cancelAnimationFrame(this._raf);
28
+ this._raf = null;
29
+ }
30
+ }
31
+
32
+ get useCanvas() {
33
+ return this._useCanvas;
34
+ }
35
+
36
+ enable() {
37
+ if (!this.requested) {
38
+ this.request();
39
+ }
40
+ }
41
+
42
+ disable() {
43
+ this.enabled = false;
44
+
45
+ if (this.stream) {
46
+ if (this.useCanvas && this._raf) {
47
+ cancelAnimationFrame(this._raf);
48
+ this._raf = null;
49
+ }
50
+
51
+ this.stream.getTracks().forEach((track) => {
52
+ track.stop();
53
+ });
54
+
55
+ this.stream = null;
56
+ }
57
+ }
58
+
59
+ prepareCanvas() {
60
+ this.canvas.width = video.videoWidth;
61
+ this.canvas.height = video.videoHeight;
62
+
63
+ this.context = this.canvas.getContext('2d');
64
+ }
65
+
66
+ drawToCanvas() {
67
+ this.context.drawImage(video, 0, 0, canvas.width, canvas.height);
68
+
69
+ this._raf = requestAnimationFrame(() => {
70
+ this.drawToCanvas();
71
+ });
72
+ }
73
+
74
+ async request() {
75
+ try {
76
+ this.stream = await navigator.mediaDevices.getUserMedia({
77
+ video: true,
78
+ audio: false,
79
+ });
80
+ this.requested = true;
81
+ this.enabled = true;
82
+
83
+ this.video.addEventListener('canplay', () => {
84
+ this.prepareCanvas();
85
+
86
+ if (this.useCanvas) {
87
+ this.drawToCanvas();
88
+ }
89
+ });
90
+ this.video.srcObject = this.stream;
91
+ this.video.play();
92
+ } catch (error) {
93
+ console.error(`Error while trying to access webcam.`);
94
+ console.log(error);
95
+ }
96
+ }
96
97
  }
97
98
 
98
99
  export default new Webcam();
@@ -1,15 +1,17 @@
1
1
  let noop = () => {};
2
2
 
3
3
  class CanvasRecorder {
4
-
5
- constructor(canvas, {
6
- duration = Infinity,
7
- framerate = 25,
8
- quality = 100,
9
- onStart = noop,
10
- onTick = noop,
11
- onComplete = noop,
12
- }) {
4
+ constructor(
5
+ canvas,
6
+ {
7
+ duration = Infinity,
8
+ framerate = 25,
9
+ quality = 100,
10
+ onStart = noop,
11
+ onTick = noop,
12
+ onComplete = noop,
13
+ },
14
+ ) {
13
15
  this.canvas = canvas;
14
16
  this.framerate = framerate;
15
17
  this.duration = duration;
@@ -19,10 +21,12 @@ class CanvasRecorder {
19
21
  this.onComplete = onComplete;
20
22
 
21
23
  this.time = 0;
22
- this.deltaTime = (1000 / this.framerate);
24
+ this.deltaTime = 1000 / this.framerate;
23
25
 
24
- this.frameDuration = (1000 / this.framerate);
25
- this.frameTotal = isFinite(duration) ? this.duration * this.framerate : Infinity;
26
+ this.frameDuration = 1000 / this.framerate;
27
+ this.frameTotal = isFinite(duration)
28
+ ? this.duration * this.framerate
29
+ : Infinity;
26
30
  this.started = false;
27
31
  this.stopped = false;
28
32
  }
@@ -39,7 +43,15 @@ class CanvasRecorder {
39
43
  return;
40
44
  }
41
45
 
42
- console.log(`CanvasRecorder - start rendering ${this.frameTotal} frames at ${this.framerate}fps for ${this.duration}s.`);
46
+ if (isFinite(this.frameTotal)) {
47
+ console.log(
48
+ `CanvasRecorder - start rendering ${this.frameTotal} frames at ${this.framerate}fps for ${this.duration}s.`,
49
+ );
50
+ } else {
51
+ console.log(
52
+ `CanvasRecorder - start rendering at ${this.framerate}fps.`,
53
+ );
54
+ }
43
55
 
44
56
  this.frameCount = 0;
45
57
  this.started = true;
@@ -61,16 +73,24 @@ class CanvasRecorder {
61
73
  frameCount: this.frameCount,
62
74
  });
63
75
 
64
- if (this.started && !this.stopped && (!isFinite(this.frameTotal) || (isFinite(this.frameTotal) && this.frameCount < this.frameTotal - 1))) {
76
+ if (
77
+ this.started &&
78
+ !this.stopped &&
79
+ (!isFinite(this.frameTotal) ||
80
+ (isFinite(this.frameTotal) &&
81
+ this.frameCount < this.frameTotal - 1))
82
+ ) {
65
83
  this.time += this.deltaTime;
66
- this.frameCount++;
67
- requestAnimationFrame(() => {
68
- this._tick()
84
+ this.frameCount++;
85
+ requestAnimationFrame(() => {
86
+ this._tick();
69
87
  });
70
- } else {
71
- console.log(`CanvasRecorder - compiling ${this.frameCount + 1} frames...`);
72
- this.end();
73
- }
88
+ } else {
89
+ console.log(
90
+ `CanvasRecorder - compiling ${this.frameCount + 1} frames...`,
91
+ );
92
+ this.end();
93
+ }
74
94
  }
75
95
 
76
96
  tick() {}
@@ -1,10 +1,9 @@
1
- import { createBlobFromDataURL } from "../../utils/file.utils";
2
- import { map } from "../../utils/math.utils";
3
- import CanvasRecorder from "./CanvasRecorder";
4
- import { exportCanvas } from "./utils";
1
+ import { createBlobFromDataURL } from '../../utils/file.utils';
2
+ import { map } from '../../utils/math.utils';
3
+ import CanvasRecorder from './CanvasRecorder';
4
+ import { exportCanvas } from './utils';
5
5
 
6
6
  class FrameRecorder extends CanvasRecorder {
7
-
8
7
  constructor(canvas, options) {
9
8
  super(canvas, options);
10
9
 
@@ -31,7 +30,9 @@ class FrameRecorder extends CanvasRecorder {
31
30
  }
32
31
 
33
32
  async end() {
34
- this.result = await Promise.all(this.frames.map((dataURL) => createBlobFromDataURL(dataURL)));
33
+ this.result = await Promise.all(
34
+ this.frames.map((dataURL) => createBlobFromDataURL(dataURL)),
35
+ );
35
36
 
36
37
  super.end();
37
38
  }
@@ -0,0 +1,45 @@
1
+ import * as HME from 'h264-mp4-encoder';
2
+ import CanvasRecorder from './CanvasRecorder';
3
+
4
+ class H264Recorder extends CanvasRecorder {
5
+ getBitmapRGBA(bitmap, width = bitmap.width, height = bitmap.height) {
6
+ this.tmpCanvas.width = width;
7
+ this.tmpCanvas.height = height;
8
+ this.tmpContext.clearRect(0, 0, width, height);
9
+ this.tmpContext.drawImage(bitmap, 0, 0, width, height);
10
+ return this.tmpContext.getImageData(0, 0, width, height).data;
11
+ }
12
+
13
+ async start() {
14
+ this.encoder = await HME.createH264MP4Encoder();
15
+
16
+ this.tmpCanvas = document.createElement('canvas');
17
+ this.tmpContext = this.tmpCanvas.getContext('2d');
18
+
19
+ this.encoder.width = this.canvas.width;
20
+ this.encoder.height = this.canvas.height;
21
+ this.encoder.frameRate = this.framerate;
22
+ this.encoder.kbps = 30000;
23
+ this.encoder.initialize();
24
+
25
+ super.start();
26
+ }
27
+
28
+ tick() {
29
+ const { width, height } = this.canvas;
30
+ const pixels = this.getBitmapRGBA(this.canvas, width, height);
31
+
32
+ this.encoder.addFrameRgba(pixels);
33
+ }
34
+
35
+ async end() {
36
+ this.encoder.finalize();
37
+
38
+ const data = this.encoder.FS.readFile(this.encoder.outputFilename);
39
+ this.result = new Blob([data], { type: 'video/mp4' });
40
+
41
+ super.end();
42
+ }
43
+ }
44
+
45
+ export default H264Recorder;