canvas-recorder-lib 0.0.0-stage → 0.6.0
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/BREAKING-CHANGES.md +18 -0
- package/README.md +232 -2
- package/dev/audio-builder.ts +273 -0
- package/dev/canvas-recorder.css +428 -0
- package/dev/canvas-recorder.ts +7930 -0
- package/dev/color-picker.ts +432 -0
- package/dev/color-utils.ts +177 -0
- package/dev/component-picker.ts +151 -0
- package/dev/delay-files.ts +139 -0
- package/dev/font-utils.ts +263 -0
- package/dev/font-widgets.ts +358 -0
- package/dev/format-editor.ts +734 -0
- package/dev/format-string.ts +110 -0
- package/dev/fourier-panel.ts +151 -0
- package/dev/handwriting-panel.ts +405 -0
- package/dev/local-file-row.ts +107 -0
- package/dev/multi-text-dialog.ts +360 -0
- package/dev/multi-text-panel.ts +235 -0
- package/dev/player-body.ts +462 -0
- package/dev/rect-snap.ts +154 -0
- package/dev/schedule-helpers.ts +332 -0
- package/dev/slide-panel.ts +57 -0
- package/dev/sound-clips.ts +198 -0
- package/dev/split-dialog.ts +504 -0
- package/dev/static-image-panel.ts +284 -0
- package/dev/style.css +41 -0
- package/dev/synced-file.ts +351 -0
- package/dev/timeline-display.ts +881 -0
- package/dev/transcribe-core.ts +305 -0
- package/dev/transcribe-worker.ts +83 -0
- package/dev/transcript-align.ts +641 -0
- package/dev/transcript-export.ts +114 -0
- package/dev/transcript-view.ts +632 -0
- package/dev/transcripts.ts +364 -0
- package/dev/video-clip-panel.ts +701 -0
- package/dist/dev/audio-builder.d.ts +89 -0
- package/dist/dev/audio-builder.d.ts.map +1 -0
- package/dist/dev/audio-builder.js +228 -0
- package/dist/dev/audio-builder.js.map +1 -0
- package/dist/dev/canvas-recorder.css +428 -0
- package/dist/dev/canvas-recorder.d.ts +2 -0
- package/dist/dev/canvas-recorder.d.ts.map +1 -0
- package/dist/dev/canvas-recorder.js +6836 -0
- package/dist/dev/canvas-recorder.js.map +1 -0
- package/dist/dev/color-picker.d.ts +5 -0
- package/dist/dev/color-picker.d.ts.map +1 -0
- package/dist/dev/color-picker.js +348 -0
- package/dist/dev/color-picker.js.map +1 -0
- package/dist/dev/color-utils.d.ts +34 -0
- package/dist/dev/color-utils.d.ts.map +1 -0
- package/dist/dev/color-utils.js +154 -0
- package/dist/dev/color-utils.js.map +1 -0
- package/dist/dev/component-picker.d.ts +19 -0
- package/dist/dev/component-picker.d.ts.map +1 -0
- package/dist/dev/component-picker.js +135 -0
- package/dist/dev/component-picker.js.map +1 -0
- package/dist/dev/delay-files.d.ts +23 -0
- package/dist/dev/delay-files.d.ts.map +1 -0
- package/dist/dev/delay-files.js +117 -0
- package/dist/dev/delay-files.js.map +1 -0
- package/dist/dev/font-utils.d.ts +50 -0
- package/dist/dev/font-utils.d.ts.map +1 -0
- package/dist/dev/font-utils.js +238 -0
- package/dist/dev/font-utils.js.map +1 -0
- package/dist/dev/font-widgets.d.ts +24 -0
- package/dist/dev/font-widgets.d.ts.map +1 -0
- package/dist/dev/font-widgets.js +299 -0
- package/dist/dev/font-widgets.js.map +1 -0
- package/dist/dev/format-editor.d.ts +75 -0
- package/dist/dev/format-editor.d.ts.map +1 -0
- package/dist/dev/format-editor.js +642 -0
- package/dist/dev/format-editor.js.map +1 -0
- package/dist/dev/format-string.d.ts +62 -0
- package/dist/dev/format-string.d.ts.map +1 -0
- package/dist/dev/format-string.js +86 -0
- package/dist/dev/format-string.js.map +1 -0
- package/dist/dev/fourier-panel.d.ts +13 -0
- package/dist/dev/fourier-panel.d.ts.map +1 -0
- package/dist/dev/fourier-panel.js +73 -0
- package/dist/dev/fourier-panel.js.map +1 -0
- package/dist/dev/handwriting-panel.d.ts +44 -0
- package/dist/dev/handwriting-panel.d.ts.map +1 -0
- package/dist/dev/handwriting-panel.js +305 -0
- package/dist/dev/handwriting-panel.js.map +1 -0
- package/dist/dev/local-file-row.d.ts +22 -0
- package/dist/dev/local-file-row.d.ts.map +1 -0
- package/dist/dev/local-file-row.js +84 -0
- package/dist/dev/local-file-row.js.map +1 -0
- package/dist/dev/multi-text-dialog.d.ts +30 -0
- package/dist/dev/multi-text-dialog.d.ts.map +1 -0
- package/dist/dev/multi-text-dialog.js +297 -0
- package/dist/dev/multi-text-dialog.js.map +1 -0
- package/dist/dev/multi-text-panel.d.ts +40 -0
- package/dist/dev/multi-text-panel.d.ts.map +1 -0
- package/dist/dev/multi-text-panel.js +155 -0
- package/dist/dev/multi-text-panel.js.map +1 -0
- package/dist/dev/player-body.d.ts +3 -0
- package/dist/dev/player-body.d.ts.map +1 -0
- package/dist/dev/player-body.js +462 -0
- package/dist/dev/player-body.js.map +1 -0
- package/dist/dev/rect-snap.d.ts +40 -0
- package/dist/dev/rect-snap.d.ts.map +1 -0
- package/dist/dev/rect-snap.js +115 -0
- package/dist/dev/rect-snap.js.map +1 -0
- package/dist/dev/schedule-helpers.d.ts +26 -0
- package/dist/dev/schedule-helpers.d.ts.map +1 -0
- package/dist/dev/schedule-helpers.js +296 -0
- package/dist/dev/schedule-helpers.js.map +1 -0
- package/dist/dev/slide-panel.d.ts +8 -0
- package/dist/dev/slide-panel.d.ts.map +1 -0
- package/dist/dev/slide-panel.js +47 -0
- package/dist/dev/slide-panel.js.map +1 -0
- package/dist/dev/sound-clips.d.ts +57 -0
- package/dist/dev/sound-clips.d.ts.map +1 -0
- package/dist/dev/sound-clips.js +175 -0
- package/dist/dev/sound-clips.js.map +1 -0
- package/dist/dev/split-dialog.d.ts +49 -0
- package/dist/dev/split-dialog.d.ts.map +1 -0
- package/dist/dev/split-dialog.js +411 -0
- package/dist/dev/split-dialog.js.map +1 -0
- package/dist/dev/static-image-panel.d.ts +33 -0
- package/dist/dev/static-image-panel.d.ts.map +1 -0
- package/dist/dev/static-image-panel.js +229 -0
- package/dist/dev/static-image-panel.js.map +1 -0
- package/dist/dev/style.css +41 -0
- package/dist/dev/synced-file.d.ts +71 -0
- package/dist/dev/synced-file.d.ts.map +1 -0
- package/dist/dev/synced-file.js +287 -0
- package/dist/dev/synced-file.js.map +1 -0
- package/dist/dev/timeline-display.d.ts +193 -0
- package/dist/dev/timeline-display.d.ts.map +1 -0
- package/dist/dev/timeline-display.js +730 -0
- package/dist/dev/timeline-display.js.map +1 -0
- package/dist/dev/transcribe-core.d.ts +63 -0
- package/dist/dev/transcribe-core.d.ts.map +1 -0
- package/dist/dev/transcribe-core.js +226 -0
- package/dist/dev/transcribe-core.js.map +1 -0
- package/dist/dev/transcribe-worker.d.ts +26 -0
- package/dist/dev/transcribe-worker.d.ts.map +1 -0
- package/dist/dev/transcribe-worker.js +48 -0
- package/dist/dev/transcribe-worker.js.map +1 -0
- package/dist/dev/transcript-align.d.ts +205 -0
- package/dist/dev/transcript-align.d.ts.map +1 -0
- package/dist/dev/transcript-align.js +497 -0
- package/dist/dev/transcript-align.js.map +1 -0
- package/dist/dev/transcript-export.d.ts +28 -0
- package/dist/dev/transcript-export.d.ts.map +1 -0
- package/dist/dev/transcript-export.js +82 -0
- package/dist/dev/transcript-export.js.map +1 -0
- package/dist/dev/transcript-view.d.ts +74 -0
- package/dist/dev/transcript-view.d.ts.map +1 -0
- package/dist/dev/transcript-view.js +556 -0
- package/dist/dev/transcript-view.js.map +1 -0
- package/dist/dev/transcripts.d.ts +65 -0
- package/dist/dev/transcripts.d.ts.map +1 -0
- package/dist/dev/transcripts.js +294 -0
- package/dist/dev/transcripts.js.map +1 -0
- package/dist/dev/video-clip-panel.d.ts +47 -0
- package/dist/dev/video-clip-panel.d.ts.map +1 -0
- package/dist/dev/video-clip-panel.js +577 -0
- package/dist/dev/video-clip-panel.js.map +1 -0
- package/dist/lib/index.d.ts +37 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +42 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/main-control.d.ts +135 -0
- package/dist/lib/main-control.d.ts.map +1 -0
- package/dist/lib/main-control.js +193 -0
- package/dist/lib/main-control.js.map +1 -0
- package/dist/src/binary-search.d.ts +206 -0
- package/dist/src/binary-search.d.ts.map +1 -0
- package/dist/src/binary-search.js +456 -0
- package/dist/src/binary-search.js.map +1 -0
- package/dist/src/corner-rounder.d.ts +17 -0
- package/dist/src/corner-rounder.d.ts.map +1 -0
- package/dist/src/corner-rounder.js +89 -0
- package/dist/src/corner-rounder.js.map +1 -0
- package/dist/src/cross-fade.d.ts +69 -0
- package/dist/src/cross-fade.d.ts.map +1 -0
- package/dist/src/cross-fade.js +113 -0
- package/dist/src/cross-fade.js.map +1 -0
- package/dist/src/debug-log.d.ts +22 -0
- package/dist/src/debug-log.d.ts.map +1 -0
- package/dist/src/debug-log.js +32 -0
- package/dist/src/debug-log.js.map +1 -0
- package/dist/src/fancy-text.d.ts +116 -0
- package/dist/src/fancy-text.d.ts.map +1 -0
- package/dist/src/fancy-text.js +211 -0
- package/dist/src/fancy-text.js.map +1 -0
- package/dist/src/glib/Cursive.json +444 -0
- package/dist/src/glib/Futura L.json +406 -0
- package/dist/src/glib/grid.d.ts +61 -0
- package/dist/src/glib/grid.d.ts.map +1 -0
- package/dist/src/glib/grid.js +134 -0
- package/dist/src/glib/grid.js.map +1 -0
- package/dist/src/glib/letters-base.d.ts +166 -0
- package/dist/src/glib/letters-base.d.ts.map +1 -0
- package/dist/src/glib/letters-base.js +186 -0
- package/dist/src/glib/letters-base.js.map +1 -0
- package/dist/src/glib/line-font.d.ts +181 -0
- package/dist/src/glib/line-font.d.ts.map +1 -0
- package/dist/src/glib/line-font.js +2978 -0
- package/dist/src/glib/line-font.js.map +1 -0
- package/dist/src/glib/my-rainbow.d.ts +126 -0
- package/dist/src/glib/my-rainbow.d.ts.map +1 -0
- package/dist/src/glib/my-rainbow.js +132 -0
- package/dist/src/glib/my-rainbow.js.map +1 -0
- package/dist/src/glib/paragraph-layout.d.ts +122 -0
- package/dist/src/glib/paragraph-layout.d.ts.map +1 -0
- package/dist/src/glib/paragraph-layout.js +377 -0
- package/dist/src/glib/paragraph-layout.js.map +1 -0
- package/dist/src/glib/path-shape-splitter.d.ts +114 -0
- package/dist/src/glib/path-shape-splitter.d.ts.map +1 -0
- package/dist/src/glib/path-shape-splitter.js +334 -0
- package/dist/src/glib/path-shape-splitter.js.map +1 -0
- package/dist/src/glib/path-shape.d.ts +890 -0
- package/dist/src/glib/path-shape.d.ts.map +1 -0
- package/dist/src/glib/path-shape.js +2276 -0
- package/dist/src/glib/path-shape.js.map +1 -0
- package/dist/src/glib/transforms.d.ts +65 -0
- package/dist/src/glib/transforms.d.ts.map +1 -0
- package/dist/src/glib/transforms.js +223 -0
- package/dist/src/glib/transforms.js.map +1 -0
- package/dist/src/interpolate.d.ts +216 -0
- package/dist/src/interpolate.d.ts.map +1 -0
- package/dist/src/interpolate.js +480 -0
- package/dist/src/interpolate.js.map +1 -0
- package/dist/src/lattice.d.ts +71 -0
- package/dist/src/lattice.d.ts.map +1 -0
- package/dist/src/lattice.js +108 -0
- package/dist/src/lattice.js.map +1 -0
- package/dist/src/local-files.d.ts +129 -0
- package/dist/src/local-files.d.ts.map +1 -0
- package/dist/src/local-files.js +327 -0
- package/dist/src/local-files.js.map +1 -0
- package/dist/src/morph-animation.d.ts +31 -0
- package/dist/src/morph-animation.d.ts.map +1 -0
- package/dist/src/morph-animation.js +441 -0
- package/dist/src/morph-animation.js.map +1 -0
- package/dist/src/peano-fourier/fourier-shared.d.ts +97 -0
- package/dist/src/peano-fourier/fourier-shared.d.ts.map +1 -0
- package/dist/src/peano-fourier/fourier-shared.js +551 -0
- package/dist/src/peano-fourier/fourier-shared.js.map +1 -0
- package/dist/src/schedule-helper.d.ts +216 -0
- package/dist/src/schedule-helper.d.ts.map +1 -0
- package/dist/src/schedule-helper.js +396 -0
- package/dist/src/schedule-helper.js.map +1 -0
- package/dist/src/showable.d.ts +789 -0
- package/dist/src/showable.d.ts.map +1 -0
- package/dist/src/showable.js +435 -0
- package/dist/src/showable.js.map +1 -0
- package/dist/src/slide-components/arrow.d.ts +36 -0
- package/dist/src/slide-components/arrow.d.ts.map +1 -0
- package/dist/src/slide-components/arrow.js +80 -0
- package/dist/src/slide-components/arrow.js.map +1 -0
- package/dist/src/slide-components/cross-fade-transition.d.ts +25 -0
- package/dist/src/slide-components/cross-fade-transition.d.ts.map +1 -0
- package/dist/src/slide-components/cross-fade-transition.js +109 -0
- package/dist/src/slide-components/cross-fade-transition.js.map +1 -0
- package/dist/src/slide-components/duration-agnostic.d.ts +23 -0
- package/dist/src/slide-components/duration-agnostic.d.ts.map +1 -0
- package/dist/src/slide-components/duration-agnostic.js +27 -0
- package/dist/src/slide-components/duration-agnostic.js.map +1 -0
- package/dist/src/slide-components/fixed-duration.d.ts +19 -0
- package/dist/src/slide-components/fixed-duration.d.ts.map +1 -0
- package/dist/src/slide-components/fixed-duration.js +24 -0
- package/dist/src/slide-components/fixed-duration.js.map +1 -0
- package/dist/src/slide-components/fourier.d.ts +175 -0
- package/dist/src/slide-components/fourier.d.ts.map +1 -0
- package/dist/src/slide-components/fourier.js +373 -0
- package/dist/src/slide-components/fourier.js.map +1 -0
- package/dist/src/slide-components/frame-counter.d.ts +15 -0
- package/dist/src/slide-components/frame-counter.d.ts.map +1 -0
- package/dist/src/slide-components/frame-counter.js +50 -0
- package/dist/src/slide-components/frame-counter.js.map +1 -0
- package/dist/src/slide-components/function-graph.d.ts +30 -0
- package/dist/src/slide-components/function-graph.d.ts.map +1 -0
- package/dist/src/slide-components/function-graph.js +111 -0
- package/dist/src/slide-components/function-graph.js.map +1 -0
- package/dist/src/slide-components/halftone-shadow.d.ts +36 -0
- package/dist/src/slide-components/halftone-shadow.d.ts.map +1 -0
- package/dist/src/slide-components/halftone-shadow.js +111 -0
- package/dist/src/slide-components/halftone-shadow.js.map +1 -0
- package/dist/src/slide-components/handwriting-effect.d.ts +94 -0
- package/dist/src/slide-components/handwriting-effect.d.ts.map +1 -0
- package/dist/src/slide-components/handwriting-effect.js +175 -0
- package/dist/src/slide-components/handwriting-effect.js.map +1 -0
- package/dist/src/slide-components/hold.d.ts +36 -0
- package/dist/src/slide-components/hold.d.ts.map +1 -0
- package/dist/src/slide-components/hold.js +48 -0
- package/dist/src/slide-components/hold.js.map +1 -0
- package/dist/src/slide-components/image.d.ts +30 -0
- package/dist/src/slide-components/image.d.ts.map +1 -0
- package/dist/src/slide-components/image.js +73 -0
- package/dist/src/slide-components/image.js.map +1 -0
- package/dist/src/slide-components/in-parallel.d.ts +243 -0
- package/dist/src/slide-components/in-parallel.d.ts.map +1 -0
- package/dist/src/slide-components/in-parallel.js +468 -0
- package/dist/src/slide-components/in-parallel.js.map +1 -0
- package/dist/src/slide-components/in-series.d.ts +56 -0
- package/dist/src/slide-components/in-series.d.ts.map +1 -0
- package/dist/src/slide-components/in-series.js +188 -0
- package/dist/src/slide-components/in-series.js.map +1 -0
- package/dist/src/slide-components/live-duration.d.ts +19 -0
- package/dist/src/slide-components/live-duration.d.ts.map +1 -0
- package/dist/src/slide-components/live-duration.js +31 -0
- package/dist/src/slide-components/live-duration.js.map +1 -0
- package/dist/src/slide-components/multi-text.d.ts +464 -0
- package/dist/src/slide-components/multi-text.d.ts.map +1 -0
- package/dist/src/slide-components/multi-text.js +1022 -0
- package/dist/src/slide-components/multi-text.js.map +1 -0
- package/dist/src/slide-components/rectangle.d.ts +23 -0
- package/dist/src/slide-components/rectangle.d.ts.map +1 -0
- package/dist/src/slide-components/rectangle.js +32 -0
- package/dist/src/slide-components/rectangle.js.map +1 -0
- package/dist/src/slide-components/registry.d.ts +90 -0
- package/dist/src/slide-components/registry.d.ts.map +1 -0
- package/dist/src/slide-components/registry.js +319 -0
- package/dist/src/slide-components/registry.js.map +1 -0
- package/dist/src/slide-components/serialize.d.ts +35 -0
- package/dist/src/slide-components/serialize.d.ts.map +1 -0
- package/dist/src/slide-components/serialize.js +39 -0
- package/dist/src/slide-components/serialize.js.map +1 -0
- package/dist/src/slide-components/show-error.d.ts +8 -0
- package/dist/src/slide-components/show-error.d.ts.map +1 -0
- package/dist/src/slide-components/show-error.js +27 -0
- package/dist/src/slide-components/show-error.js.map +1 -0
- package/dist/src/slide-components/simple-text.d.ts +30 -0
- package/dist/src/slide-components/simple-text.d.ts.map +1 -0
- package/dist/src/slide-components/simple-text.js +101 -0
- package/dist/src/slide-components/simple-text.js.map +1 -0
- package/dist/src/slide-components/slide-component.d.ts +58 -0
- package/dist/src/slide-components/slide-component.d.ts.map +1 -0
- package/dist/src/slide-components/slide-component.js +154 -0
- package/dist/src/slide-components/slide-component.js.map +1 -0
- package/dist/src/slide-components/slide-left.d.ts +18 -0
- package/dist/src/slide-components/slide-left.d.ts.map +1 -0
- package/dist/src/slide-components/slide-left.js +40 -0
- package/dist/src/slide-components/slide-left.js.map +1 -0
- package/dist/src/slide-components/split-video-clip.d.ts +82 -0
- package/dist/src/slide-components/split-video-clip.d.ts.map +1 -0
- package/dist/src/slide-components/split-video-clip.js +178 -0
- package/dist/src/slide-components/split-video-clip.js.map +1 -0
- package/dist/src/slide-components/text-effect.d.ts +83 -0
- package/dist/src/slide-components/text-effect.d.ts.map +1 -0
- package/dist/src/slide-components/text-effect.js +20 -0
- package/dist/src/slide-components/text-effect.js.map +1 -0
- package/dist/src/slide-components/traditional-text.d.ts +129 -0
- package/dist/src/slide-components/traditional-text.d.ts.map +1 -0
- package/dist/src/slide-components/traditional-text.js +313 -0
- package/dist/src/slide-components/traditional-text.js.map +1 -0
- package/dist/src/slide-components/video-clip.d.ts +75 -0
- package/dist/src/slide-components/video-clip.d.ts.map +1 -0
- package/dist/src/slide-components/video-clip.js +605 -0
- package/dist/src/slide-components/video-clip.js.map +1 -0
- package/dist/src/slide-components/video-info.d.ts +81 -0
- package/dist/src/slide-components/video-info.d.ts.map +1 -0
- package/dist/src/slide-components/video-info.js +122 -0
- package/dist/src/slide-components/video-info.js.map +1 -0
- package/dist/src/slow-image-sources.d.ts +93 -0
- package/dist/src/slow-image-sources.d.ts.map +1 -0
- package/dist/src/slow-image-sources.js +179 -0
- package/dist/src/slow-image-sources.js.map +1 -0
- package/dist/src/snapshot.d.ts +129 -0
- package/dist/src/snapshot.d.ts.map +1 -0
- package/dist/src/snapshot.js +431 -0
- package/dist/src/snapshot.js.map +1 -0
- package/dist/src/stroke-colors.d.ts +84 -0
- package/dist/src/stroke-colors.d.ts.map +1 -0
- package/dist/src/stroke-colors.js +360 -0
- package/dist/src/stroke-colors.js.map +1 -0
- package/dist/src/utility.d.ts +266 -0
- package/dist/src/utility.d.ts.map +1 -0
- package/dist/src/utility.js +647 -0
- package/dist/src/utility.js.map +1 -0
- package/dist/src/zipper.d.ts +38 -0
- package/dist/src/zipper.d.ts.map +1 -0
- package/dist/src/zipper.js +109 -0
- package/dist/src/zipper.js.map +1 -0
- package/imported-files.txt +85 -0
- package/internals.md +439 -0
- package/lib/index.ts +44 -0
- package/lib/main-control.ts +252 -0
- package/package.json +56 -4
- package/src/binary-search.ts +480 -0
- package/src/corner-rounder.ts +128 -0
- package/src/cross-fade.ts +146 -0
- package/src/debug-log.ts +40 -0
- package/src/fancy-text.ts +255 -0
- package/src/glib/Cursive.json +444 -0
- package/src/glib/Futura L.json +406 -0
- package/src/glib/grid.ts +215 -0
- package/src/glib/letters-base.ts +243 -0
- package/src/glib/line-font.ts +3344 -0
- package/src/glib/my-rainbow.ts +217 -0
- package/src/glib/paragraph-layout.ts +407 -0
- package/src/glib/path-shape-splitter.ts +380 -0
- package/src/glib/path-shape.ts +2838 -0
- package/src/glib/transforms.ts +280 -0
- package/src/interpolate.ts +598 -0
- package/src/lattice.ts +157 -0
- package/src/local-files.ts +389 -0
- package/src/morph-animation.ts +481 -0
- package/src/peano-fourier/fft-js.d.ts +3 -0
- package/src/peano-fourier/fourier-shared.ts +666 -0
- package/src/schedule-helper.ts +429 -0
- package/src/showable.ts +1100 -0
- package/src/slide-components/arrow.ts +115 -0
- package/src/slide-components/cross-fade-transition.ts +122 -0
- package/src/slide-components/duration-agnostic.ts +36 -0
- package/src/slide-components/fixed-duration.ts +24 -0
- package/src/slide-components/fourier.ts +514 -0
- package/src/slide-components/frame-counter.ts +55 -0
- package/src/slide-components/function-graph.ts +149 -0
- package/src/slide-components/halftone-shadow.ts +144 -0
- package/src/slide-components/handwriting-effect.ts +230 -0
- package/src/slide-components/hold.ts +72 -0
- package/src/slide-components/image.ts +87 -0
- package/src/slide-components/in-parallel.ts +590 -0
- package/src/slide-components/in-series.ts +238 -0
- package/src/slide-components/live-duration.ts +31 -0
- package/src/slide-components/multi-text.ts +1288 -0
- package/src/slide-components/rectangle.ts +41 -0
- package/src/slide-components/registry.ts +422 -0
- package/src/slide-components/serialize.ts +68 -0
- package/src/slide-components/show-error.ts +28 -0
- package/src/slide-components/simple-text.ts +157 -0
- package/src/slide-components/slide-component.ts +197 -0
- package/src/slide-components/slide-left.ts +51 -0
- package/src/slide-components/split-video-clip.ts +228 -0
- package/src/slide-components/text-effect.ts +93 -0
- package/src/slide-components/traditional-text.ts +378 -0
- package/src/slide-components/video-clip.ts +733 -0
- package/src/slide-components/video-info.ts +188 -0
- package/src/slow-image-sources.ts +199 -0
- package/src/snapshot.ts +547 -0
- package/src/stroke-colors.ts +495 -0
- package/src/utility.ts +724 -0
- package/src/zipper.ts +134 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Breaking Changes
|
|
2
|
+
|
|
3
|
+
Changes that can break a project using this library, newest first, and what to do about each.
|
|
4
|
+
This is not a full list of changes: new features and fixes aren't here.
|
|
5
|
+
|
|
6
|
+
While the version is 0.x, every breaking change comes with a new minor version (0.5 → 0.6), and nothing else does.
|
|
7
|
+
A project that depends on `"^0.5.0"` gets 0.5.x updates but never 0.6.0, so it only meets these changes when someone installs the new version on purpose.
|
|
8
|
+
|
|
9
|
+
## 0.6.0 (2026-11-05)
|
|
10
|
+
|
|
11
|
+
- **`CANVAS_WIDTH_UNITS`, `CANVAS_HEIGHT_UNITS` and `PIXELS_PER_UNIT` are gone** from `src/slide-components/video-info`.
|
|
12
|
+
The video's size can change now, from the GUI or from `canvasRecorder.size`, so a constant can't hold it.
|
|
13
|
+
Use `canvasWidthUnits()`, `canvasHeightUnits()` and `pixelsPerUnit()` instead, and call them while drawing, not when the module loads.
|
|
14
|
+
To keep the old fixed size, set it in your main program and call `canvasRecorder.lockSize()`.
|
|
15
|
+
|
|
16
|
+
## 0.5.0 (2026-10-18)
|
|
17
|
+
|
|
18
|
+
The first version on npm.
|
package/README.md
CHANGED
|
@@ -1,3 +1,233 @@
|
|
|
1
|
-
#
|
|
1
|
+
# canvas-recorder-lib
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Preview, edit, annotate and record HTML canvas animations.
|
|
4
|
+
This is the tooling from [canvas-recorder](https://github.com/TradeIdeasPhilip/canvas-recorder), packaged as a library, so each video can be its own small project.
|
|
5
|
+
|
|
6
|
+
**Status:** in use for published videos.
|
|
7
|
+
It's still growing: while the version is 0.x, a change that could break your project comes with a new minor version (0.5 → 0.6), and [BREAKING-CHANGES.md](BREAKING-CHANGES.md) says what changed and how to update.
|
|
8
|
+
|
|
9
|
+
**Source:** https://github.com/TradeIdeasPhilip/canvas-recorder-lib
|
|
10
|
+
|
|
11
|
+
**API docs:** https://tradeideasphilip.github.io/canvas-recorder-lib/
|
|
12
|
+
|
|
13
|
+
**Try it:** the [Simple Video Editor](https://tradeideasphilip.github.io/canvas-recorder-lib/simple-video-editor/) is this library's player and Visual Editor with an empty project: open video clips from your computer, add text and callouts, and save an MP4.
|
|
14
|
+
Its source is in [simple-video-editor/](https://github.com/TradeIdeasPhilip/canvas-recorder-lib/tree/master/simple-video-editor).
|
|
15
|
+
|
|
16
|
+
**Showcase:** the [showcase](https://tradeideasphilip.github.io/canvas-recorder-lib/tests/showcase.html) is a series of slides that show what the library can do, one topic per slide.
|
|
17
|
+
Looking for code or ideas to copy? Start there.
|
|
18
|
+
Its source is in [test-project/src/showcase/](https://github.com/TradeIdeasPhilip/canvas-recorder-lib/tree/master/test-project/src/showcase), one file per slide.
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { canvasRecorder } from "canvas-recorder-lib";
|
|
24
|
+
import { myVideo } from "./my-video.ts"; // Any Showable.
|
|
25
|
+
|
|
26
|
+
// Optional. The defaults are 3840 × 2160 pixels, 16 × 9 units, 60 fps.
|
|
27
|
+
// canvasRecorder.pixelWidth = 1920;
|
|
28
|
+
// canvasRecorder.pixelHeight = 1080;
|
|
29
|
+
|
|
30
|
+
// Optional. For a video drawn for one specific size.
|
|
31
|
+
// canvasRecorder.lockSize();
|
|
32
|
+
|
|
33
|
+
canvasRecorder.start(myVideo, { name: "my-video" });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The player shows the video's size (pixels and units) in four fields.
|
|
37
|
+
Unless the program calls `lockSize()`, the user can change them, and the player remembers the change for this video.
|
|
38
|
+
So read `canvasRecorder.width` and the rest while drawing, not once at startup.
|
|
39
|
+
The Save dialog can also save one file at a different size, without changing the video.
|
|
40
|
+
|
|
41
|
+
`start()` replaces the page's `<body>` with the player and its Visual Editor.
|
|
42
|
+
Leave your HTML's body empty, or put "Please wait…" in it.
|
|
43
|
+
|
|
44
|
+
- Don't `await` `start()` at the top level of your main module. The page would hang with no error message.
|
|
45
|
+
- `name` keeps this video's saved editor state separate from other videos served from the same origin.
|
|
46
|
+
|
|
47
|
+
Imports:
|
|
48
|
+
|
|
49
|
+
- `canvas-recorder-lib` exports the main control object and the common building blocks.
|
|
50
|
+
- Anything else is available by path, e.g. `canvas-recorder-lib/src/glib/path-shape`.
|
|
51
|
+
|
|
52
|
+
The package includes the TypeScript source next to the compiled JavaScript, and Go to Definition takes you to the source.
|
|
53
|
+
|
|
54
|
+
## Notes for a project that uses this library
|
|
55
|
+
|
|
56
|
+
Things the first project to use this library ([riemann-surfaces-3d](https://github.com/TradeIdeasPhilip/riemann-surfaces-3d)) ran into.
|
|
57
|
+
|
|
58
|
+
### Installing
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install canvas-recorder-lib
|
|
62
|
+
npm install-scripts deny onnxruntime-node protobufjs
|
|
63
|
+
npm install-scripts ls # should say "No packages with unreviewed install scripts."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **Depend on the published version,** not `"file:../canvas-recorder-lib"`.
|
|
67
|
+
A `file:` dependency works on your machine, but GitHub's build machine has no `../canvas-recorder-lib`, so your GitHub Pages deploy fails.
|
|
68
|
+
`npm ci` doesn't notice; `tsc` stops with "Cannot find module 'canvas-recorder-lib'."
|
|
69
|
+
- **Deny those two install scripts.** npm 11 skips dependencies' install scripts until your project approves or denies them, and warns about each one.
|
|
70
|
+
Both come from `@huggingface/transformers`, which the library uses for transcription, and neither is needed in a browser.
|
|
71
|
+
`deny` records that in your `package.json`'s `allowScripts`.
|
|
72
|
+
- That field replaces any `allow-scripts` setting in your `~/.npmrc`.
|
|
73
|
+
If your `~/.npmrc` approves `fsevents` (Vite's file watcher on macOS), approve it again here: `npm install-scripts approve fsevents --no-allow-scripts-pin`.
|
|
74
|
+
In a new project that fails with `ENOMATCH`, because the first `npm install` doesn't put `fsevents` in `node_modules`.
|
|
75
|
+
Run `npm install` again (it warns that `fsevents` isn't approved), then approve it.
|
|
76
|
+
- **Expect a big `package-lock.json`.** The library's dependencies are installed in your project, including some that only Node.js uses.
|
|
77
|
+
|
|
78
|
+
### Working on the library at the same time
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm link ../canvas-recorder-lib # in your project
|
|
82
|
+
npm run watch # in canvas-recorder-lib
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `npm link` replaces `node_modules/canvas-recorder-lib` with a symlink to your copy, without touching `package.json` or `package-lock.json`.
|
|
86
|
+
So your deploy keeps using the published version.
|
|
87
|
+
- `npm ls canvas-recorder-lib` shows `-> ./../canvas-recorder-lib` while the link is active.
|
|
88
|
+
- Any later `npm install` or `npm ci` quietly puts the published copy back.
|
|
89
|
+
- Your project uses the library's compiled `dist/`, so keep `npm run watch` running in the library.
|
|
90
|
+
- When the library change is ready, publish a new version, then `npm install canvas-recorder-lib@<version>` in your project and commit both package files.
|
|
91
|
+
|
|
92
|
+
### Vite setup
|
|
93
|
+
|
|
94
|
+
Add this to your `vite.config.js`:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import { defineConfig, searchForWorkspaceRoot } from "vite";
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
base: "./",
|
|
101
|
+
resolve: {
|
|
102
|
+
dedupe: ["phil-lib"],
|
|
103
|
+
},
|
|
104
|
+
server: {
|
|
105
|
+
port: 5174, // Pick one per project, and keep it.
|
|
106
|
+
strictPort: true,
|
|
107
|
+
fs: {
|
|
108
|
+
allow: [searchForWorkspaceRoot(process.cwd()), "../canvas-recorder-lib"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
optimizeDeps: {
|
|
112
|
+
include: ["canvas-recorder-lib > @huggingface/transformers"],
|
|
113
|
+
},
|
|
114
|
+
build: {
|
|
115
|
+
rolldownOptions: { output: { keepNames: true } }, // Vite 8. Vite 7: rollupOptions.
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **`base: "./"`:** relative paths, so the build works on GitHub Pages, or in any subdirectory.
|
|
121
|
+
- **`dedupe`:** one copy of phil-lib. With the library linked, Vite would otherwise load two: yours, and the one in the library's `node_modules`.
|
|
122
|
+
- **`fs.allow`:** needed while the library is linked.
|
|
123
|
+
Without it, the server console says the transcription worker is "outside of Vite serving allow list", and transcripts fail.
|
|
124
|
+
A linked library lives outside your project. Vite serves files that are imported, but the worker is loaded by URL.
|
|
125
|
+
Setting `allow` turns off Vite's default, so your own folder has to be listed too; that's what `searchForWorkspaceRoot()` is for.
|
|
126
|
+
- **`optimizeDeps.include`:** without it, the first Import audio or transcript reloads the page, under `npm run dev` only.
|
|
127
|
+
Only the transcription worker imports that package, and Vite doesn't look inside workers when it starts, so it finds the package late and reloads to use it.
|
|
128
|
+
- **`keepNames`:** the Visual Editor writes TypeScript that names each component's class (Sync diffs, and `properties/*.txt`).
|
|
129
|
+
In a minified production build, like your GitHub Pages copy, those names come out as `hi` and `Nr` without it.
|
|
130
|
+
- **A fixed port:** the editor saves your work in the browser (IndexedDB), and the browser keeps a separate copy for each origin.
|
|
131
|
+
If another project already has the port, Vite quietly moves to the next one (5173 → 5174), and the editor seems to forget everything.
|
|
132
|
+
`strictPort` makes that an error instead.
|
|
133
|
+
|
|
134
|
+
### Ports
|
|
135
|
+
|
|
136
|
+
Each project that uses this library gets its own port, forever.
|
|
137
|
+
Changing the port loses the editor's saved work, because IndexedDB belongs to the origin, and the port is part of the origin.
|
|
138
|
+
For a new project, take the next number and add it here.
|
|
139
|
+
|
|
140
|
+
| Port | Project |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| 5173 | [canvas-recorder](https://github.com/TradeIdeasPhilip/canvas-recorder) (Vite's default; historical) |
|
|
143
|
+
| 5174 | [riemann-surfaces-3d](https://github.com/TradeIdeasPhilip/riemann-surfaces-3d) |
|
|
144
|
+
| 5175 | [affine-transforms](https://github.com/TradeIdeasPhilip/affine-transforms) |
|
|
145
|
+
| 5176 | [`test-project/`](https://github.com/TradeIdeasPhilip/canvas-recorder-lib/tree/master/test-project) in this repo: the showcase, tests and tools |
|
|
146
|
+
| 5177 | [`simple-video-editor/`](https://github.com/TradeIdeasPhilip/canvas-recorder-lib/tree/master/simple-video-editor) in this repo |
|
|
147
|
+
| 5178 | 7-pointed-star |
|
|
148
|
+
|
|
149
|
+
### Starting the editor
|
|
150
|
+
|
|
151
|
+
- **Load the library only when you need it,** e.g. `await import("./video-editor")` from a button.
|
|
152
|
+
The rest of your program never pays for it.
|
|
153
|
+
- **Don't `await canvasRecorder.start()` at the top level of a module.** The page hangs with no error message.
|
|
154
|
+
- **Turn off your own CSS first.** `start()` replaces your `<body>`, but your stylesheets are still there, and they restyle the editor.
|
|
155
|
+
Disabling every sheet that has more than `@font-face` rules works (see `hideOurStyles()` in riemann-surfaces-3d's `src/video-editor.ts`).
|
|
156
|
+
- **Stop your own animation loop and keyboard handlers.** Otherwise typing in the Visual Editor's text boxes drives your program.
|
|
157
|
+
- **Put sound files in your `public/` folder** and refer to them as `"./name.m4a"`.
|
|
158
|
+
- **Or use files on your computer.** The Video File and Image File panels have **📂 Open file…**, which stores a URL like `local:Screen Recording.mov`.
|
|
159
|
+
The browser remembers the file, but only in this browser on this computer.
|
|
160
|
+
After a reload it may want your OK again: click **🔓 Allow local files**.
|
|
161
|
+
The Save dialog checks every local file before it starts, so a long recording can't stop halfway to ask.
|
|
162
|
+
In your own code, read media with `fetchMedia()`, which understands `local:` URLs.
|
|
163
|
+
- **Save the editor's state into your project, so it's in git.** The editor always saves inside the browser (IndexedDB), which git can't see.
|
|
164
|
+
Make a `properties/` folder, and point **💾 Save As**, the **Save defaults** checkbox, and **Save Diffs** at it.
|
|
165
|
+
The browser remembers each choice, so after the first time they keep saving there.
|
|
166
|
+
Add a `properties/README.md` explaining the three files; copy [riemann-surfaces-3d's](https://github.com/TradeIdeasPhilip/riemann-surfaces-3d/blob/master/properties/README.md).
|
|
167
|
+
- **Register your own components** with `componentRegistry.set(key, { create })`.
|
|
168
|
+
Then the Visual Editor can add, save and load them, and "TypeScript defaults" can rebuild them.
|
|
169
|
+
- **A mistake in the Visual Editor is a state, not an error.** Your components should treat it that way:
|
|
170
|
+
- Show a warning, in the editor or on the canvas. Never throw, and never a dialog box.
|
|
171
|
+
- Save it like any other state. Editing between two good states often passes through bad ones.
|
|
172
|
+
- Draw as much as still makes sense. Values in worse shape can come back from IndexedDB or a file, so fail gracefully and make them easy to fix.
|
|
173
|
+
- Throwing while saving a video is only for external failures, like an image or video that doesn't load.
|
|
174
|
+
- Assertions are still fine for the programmer's own mistakes, like a table of values checked when the program starts.
|
|
175
|
+
- **Name a replaceable child with `userEditableDescription`,** not the constructor's `description`, when your code adds one with `replaceableComponents.push()`.
|
|
176
|
+
When the editor loads saved changes, it rebuilds those children from the registry, so a `description` from your constructor comes back as the registry's default.
|
|
177
|
+
`userEditableDescription` is saved with the rest, and the diff file uses it to match each child with its TypeScript.
|
|
178
|
+
|
|
179
|
+
### Recording a program that has its own state
|
|
180
|
+
|
|
181
|
+
Most of canvas-recorder's components draw any time you ask, in any order.
|
|
182
|
+
A program that steps its own animation forward (physics, a game loop, three.js robots with smooth walking) can't do that.
|
|
183
|
+
riemann-surfaces-3d's `src/full-screen-component.ts` handles this, and it's meant to be copied as is. What it learned:
|
|
184
|
+
|
|
185
|
+
- **Step with a fixed dt** (1 / `canvasRecorder.fps`), never the clock. Then recording, preview and every re-run match.
|
|
186
|
+
- **Going backwards means restart and replay.** Recording always moves forward, so it's always exact.
|
|
187
|
+
The live preview spends a few milliseconds per frame catching up, and shows a warning light until it does.
|
|
188
|
+
- **Take one step right after a restart.** Many programs only update their scene (camera, headings) when they step.
|
|
189
|
+
Without that step, frame 0 shows whatever was on screen before the restart, and jumps to frame 1.
|
|
190
|
+
- **Parents call `getFramePromises()` on every child, even ones that aren't on screen.**
|
|
191
|
+
A component that steps a program there must ignore times outside `0` to `duration`, or two of them in a series will keep restarting each other.
|
|
192
|
+
- **Only the player calls `show()`.** There are no thumbnails or other random-time calls.
|
|
193
|
+
|
|
194
|
+
### Size
|
|
195
|
+
|
|
196
|
+
The transcription feature brings a 27 MB speech-recognition file (`ort-wasm…wasm`) into your production build.
|
|
197
|
+
It is only downloaded if someone uses transcription.
|
|
198
|
+
|
|
199
|
+
## How this library is made
|
|
200
|
+
|
|
201
|
+
The code came from [canvas-recorder](https://github.com/TradeIdeasPhilip/canvas-recorder), which is now historical.
|
|
202
|
+
Since 2026-10-14, this library is the master copy: new features and fixes start here.
|
|
203
|
+
`imported-files.txt` lists what was copied, and from which canvas-recorder commit.
|
|
204
|
+
|
|
205
|
+
## Build
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
npm install
|
|
209
|
+
npm run build # or: npm run watch
|
|
210
|
+
npm run clean # empty dist/
|
|
211
|
+
npm run docs # API docs, in docs/
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Output goes to `dist/`: `.js`, `.d.ts`, source maps, plus the CSS and JSON files the JavaScript imports.
|
|
215
|
+
|
|
216
|
+
Every relative import names its file, like `from "./utility.ts"`, and `npm run build` stops if one doesn't.
|
|
217
|
+
tsc turns `.ts` into `.js` in `dist/`, which works everywhere; an import with no extension only works where a tool like Vite guesses it.
|
|
218
|
+
|
|
219
|
+
`build` and `watch` only add and overwrite files in `dist/`, so a project's running dev server never sees it empty.
|
|
220
|
+
A deleted or renamed source file leaves its old output behind until `npm run clean`.
|
|
221
|
+
|
|
222
|
+
A project that has linked this library (see [Working on the library at the same time](#working-on-the-library-at-the-same-time)) sees a change as soon as `npm run watch` recompiles it.
|
|
223
|
+
|
|
224
|
+
The Simple Video Editor is a separate npm project inside this repo. [simple-video-editor/README.md](simple-video-editor/README.md) says how to run it.
|
|
225
|
+
|
|
226
|
+
## Releasing
|
|
227
|
+
|
|
228
|
+
1. Bump the version. If [BREAKING-CHANGES.md](BREAKING-CHANGES.md) has an "Unreleased" section: `npm version minor --no-git-tag-version`, and replace "Unreleased" with the new version and the date.
|
|
229
|
+
Otherwise: `npm version patch --no-git-tag-version`. Commit and push.
|
|
230
|
+
2. `npm publish`. `prepublishOnly` empties and rebuilds `dist/` first, so nothing stale is published.
|
|
231
|
+
3. In each project: `npm install canvas-recorder-lib@<version>`, `npx tsc --noEmit`, and commit `package.json` and `package-lock.json`.
|
|
232
|
+
|
|
233
|
+
The API docs publish themselves on every push to master.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { fetchMedia } from "../src/local-files.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This creates a complete audio clip by joining multiple clips.
|
|
5
|
+
* It starts with all silence and you can add the clips wherever you need them.
|
|
6
|
+
*
|
|
7
|
+
* Overlapping clips are mixed: each one is added to whatever is already
|
|
8
|
+
* there, so two sounds at the same time are both heard.
|
|
9
|
+
*
|
|
10
|
+
* The result is always **mono**. Every source is mixed down to one channel as
|
|
11
|
+
* it is added. Nothing recorded for these videos is really stereo: a Mac
|
|
12
|
+
* screen recording, for example, writes its one microphone signal into both
|
|
13
|
+
* channels, and CapCut's exports have done the same. One channel is less to
|
|
14
|
+
* test and can't put a sound in only one ear by accident.
|
|
15
|
+
*/
|
|
16
|
+
export class AudioBuilder {
|
|
17
|
+
/**
|
|
18
|
+
* Like AudioContext, but this lets you process data at full speed.
|
|
19
|
+
* AudioContext doesn't allow you to process things faster than realtime.
|
|
20
|
+
*/
|
|
21
|
+
private audioContext: OfflineAudioContext;
|
|
22
|
+
/**
|
|
23
|
+
* This is the sound we are building.
|
|
24
|
+
*/
|
|
25
|
+
private buffer: AudioBuffer;
|
|
26
|
+
|
|
27
|
+
getAudioBuffer(): AudioBuffer {
|
|
28
|
+
return this.buffer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor(totalDurationMs: number) {
|
|
32
|
+
const sampleRate = 48000;
|
|
33
|
+
// OfflineAudioContext requires length >= 1 -- a project can legitimately
|
|
34
|
+
// start out at duration 0 (e.g. an empty timeline before any clips have
|
|
35
|
+
// been added yet), so clamp rather than let the constructor throw.
|
|
36
|
+
this.audioContext = new OfflineAudioContext({
|
|
37
|
+
length: Math.max(1, totalDurationMs * 1000),
|
|
38
|
+
sampleRate,
|
|
39
|
+
});
|
|
40
|
+
const totalSamples = Math.max(
|
|
41
|
+
1,
|
|
42
|
+
Math.ceil((totalDurationMs / 1000) * sampleRate),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
this.buffer = this.audioContext.createBuffer(1, totalSamples, sampleRate);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async #createNewAudioBuffer(url: string) {
|
|
49
|
+
const time1 = performance.now();
|
|
50
|
+
const response = await fetchMedia(url);
|
|
51
|
+
const time2 = performance.now();
|
|
52
|
+
const encodedSource = await response.arrayBuffer();
|
|
53
|
+
const time3 = performance.now();
|
|
54
|
+
// decodeAudioData() takes about ⅜ - ¾ of a second to decode 4 minutes and 18 seconds of audio.
|
|
55
|
+
const sourceBuffer = await this.audioContext.decodeAudioData(encodedSource);
|
|
56
|
+
const time4 = performance.now();
|
|
57
|
+
const fileSizeKB = encodedSource.byteLength / 1024;
|
|
58
|
+
false &&
|
|
59
|
+
console.log(
|
|
60
|
+
`[audio] fetch: ${(time2 - time1).toFixed(0)} ms (${fileSizeKB.toFixed(0)} KB, ` +
|
|
61
|
+
`${((time2 - time1) / fileSizeKB).toFixed(3)} ms/KB) | ` +
|
|
62
|
+
`arrayBuffer: ${(time3 - time2).toFixed(0)} ms | ` +
|
|
63
|
+
`decode: ${(time4 - time3).toFixed(0)} ms`,
|
|
64
|
+
);
|
|
65
|
+
return sourceBuffer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
readonly #cache = new Map<string, Promise<AudioBuffer>>();
|
|
69
|
+
readonly #resolved = new Map<string, AudioBuffer>();
|
|
70
|
+
|
|
71
|
+
clearCache() {
|
|
72
|
+
this.#cache.clear();
|
|
73
|
+
this.#resolved.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
warmCache(url: string) {
|
|
77
|
+
this.#findAudioBuffer(url);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Returns the decoded buffer synchronously if already cached, otherwise null. */
|
|
81
|
+
getDecodedBuffer(url: string): AudioBuffer | null {
|
|
82
|
+
return this.#resolved.get(url) ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#findAudioBuffer(url: string): Promise<AudioBuffer> {
|
|
86
|
+
return this.#cache.getOrInsertComputed(url, (missingUrl) =>
|
|
87
|
+
this.#createNewAudioBuffer(missingUrl).then((buf) => {
|
|
88
|
+
this.#resolved.set(missingUrl, buf);
|
|
89
|
+
return buf;
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Add a new clip to the soundtrack.
|
|
96
|
+
* It is mixed with (added to) any sounds already at that position.
|
|
97
|
+
*
|
|
98
|
+
* The sum can go past ±1, which clips. That's fine for what this is used
|
|
99
|
+
* for, a voiceover with the occasional overlap; the WAV encoder clamps, and
|
|
100
|
+
* live playback clips the same way.
|
|
101
|
+
* @param url Where to find the file.
|
|
102
|
+
*
|
|
103
|
+
* I typically use vite dev mode to run my project.
|
|
104
|
+
* I put the files in the `/public` directory of the project.
|
|
105
|
+
* And the url will start with "./".
|
|
106
|
+
*
|
|
107
|
+
* CORS can be an issue depending on the server.
|
|
108
|
+
* A `local:` URL reads a file on the user's computer. See src/local-files.ts.
|
|
109
|
+
* @param startMsInDestination 0 to play the new clip at the the beginning of the video.
|
|
110
|
+
* 1000 to start this clip 1 second after the video starts.
|
|
111
|
+
*
|
|
112
|
+
* Negative numbers are allowed: the part of the clip that would play before
|
|
113
|
+
* the video starts is skipped. A sound clip may legally start before the
|
|
114
|
+
* component that owns it, and that component may be at the very start.
|
|
115
|
+
* @param trimFromStartMs Where to start the clip.
|
|
116
|
+
* 0 to play the entire clip.
|
|
117
|
+
* 500 to trim the first half second from the clip.
|
|
118
|
+
* The default is 0.
|
|
119
|
+
*
|
|
120
|
+
* Negative numbers mean silence before the clip starts: -500 starts the
|
|
121
|
+
* clip half a second after `startMsInDestination`.
|
|
122
|
+
*
|
|
123
|
+
* If you fast forward the resulting video to `startMsInDestination` in your video player,
|
|
124
|
+
* and you fast forward the initial clip in another player to `trimFromStartMs`,
|
|
125
|
+
* and you hit play in both at the same time,
|
|
126
|
+
* they'd play the same thing.
|
|
127
|
+
*
|
|
128
|
+
* **Video files and edit lists**: `decodeAudioData()` ignores the MP4/MOV container's
|
|
129
|
+
* edit list. If the source is a video file (e.g. a Mac screen recording), the audio
|
|
130
|
+
* samples start at media-time 0, but the *presentation* timeline may start later.
|
|
131
|
+
* The caller is responsible for computing the offset from
|
|
132
|
+
* `InputTrack.getFirstTimestamp()` (Mediabunny) and adding it to `trimFromStartMs`
|
|
133
|
+
* so the audio aligns with the video's visible frames.
|
|
134
|
+
* See `development-plans/import-audio-button.md` for the full plan.
|
|
135
|
+
* @param length How much of this clip to include.
|
|
136
|
+
* Stop copying this many milliseconds after `trimFromStartMs`.
|
|
137
|
+
*
|
|
138
|
+
* This value will be clamped to a reasonable range.
|
|
139
|
+
* Requesting 0 or fewer milliseconds means to copy nothing.
|
|
140
|
+
* We stop copying when we get to this much time,
|
|
141
|
+
* the end of the source, or the end of the destination,
|
|
142
|
+
* whichever comes first.
|
|
143
|
+
*
|
|
144
|
+
* The default is `Infinity`, to copy the entire clip or as much as will fit.
|
|
145
|
+
* @returns This promise will resolve (or reject) when the request is complete.
|
|
146
|
+
*/
|
|
147
|
+
async add(
|
|
148
|
+
url: string,
|
|
149
|
+
startMsInDestination: number,
|
|
150
|
+
trimFromStartMs: number = 0,
|
|
151
|
+
length: number = Infinity,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
if (startMsInDestination < 0) {
|
|
154
|
+
// Skip the part that would play before the video starts.
|
|
155
|
+
trimFromStartMs -= startMsInDestination;
|
|
156
|
+
length += startMsInDestination;
|
|
157
|
+
startMsInDestination = 0;
|
|
158
|
+
}
|
|
159
|
+
if (trimFromStartMs < 0) {
|
|
160
|
+
// Nothing to play before the source starts: start later instead.
|
|
161
|
+
startMsInDestination -= trimFromStartMs;
|
|
162
|
+
length += trimFromStartMs;
|
|
163
|
+
trimFromStartMs = 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sourceBuffer = await this.#findAudioBuffer(url);
|
|
167
|
+
|
|
168
|
+
const samplesPerMs = this.buffer.sampleRate / 1000;
|
|
169
|
+
const destinationStart = Math.floor(startMsInDestination * samplesPerMs);
|
|
170
|
+
const sourceStart = Math.floor(trimFromStartMs * samplesPerMs);
|
|
171
|
+
// Stop at whichever runs out first: the requested length, the source, or
|
|
172
|
+
// the destination. (decodeAudioData() already resampled the source to
|
|
173
|
+
// our sample rate, so one count works for both.)
|
|
174
|
+
const count = Math.max(
|
|
175
|
+
0,
|
|
176
|
+
Math.min(
|
|
177
|
+
Math.floor(length * samplesPerMs),
|
|
178
|
+
sourceBuffer.length - sourceStart,
|
|
179
|
+
this.buffer.length - destinationStart,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
if (count === 0) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const destination = this.buffer
|
|
186
|
+
.getChannelData(0)
|
|
187
|
+
.subarray(destinationStart, destinationStart + count);
|
|
188
|
+
const channels = Array.from(
|
|
189
|
+
{ length: sourceBuffer.numberOfChannels },
|
|
190
|
+
(_, channel) =>
|
|
191
|
+
sourceBuffer
|
|
192
|
+
.getChannelData(channel)
|
|
193
|
+
.subarray(sourceStart, sourceStart + count),
|
|
194
|
+
);
|
|
195
|
+
if (channels.length === 1) {
|
|
196
|
+
// The common case.
|
|
197
|
+
const source = channels[0];
|
|
198
|
+
for (let i = 0; i < count; i++) {
|
|
199
|
+
destination[i] += source[i];
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Average the channels, don't sum them: identical channels (the usual
|
|
203
|
+
// "stereo") come out exactly as they went in, where a sum would double
|
|
204
|
+
// the level and clip.
|
|
205
|
+
const scale = 1 / channels.length;
|
|
206
|
+
for (let i = 0; i < count; i++) {
|
|
207
|
+
let sum = 0;
|
|
208
|
+
for (const channel of channels) {
|
|
209
|
+
sum += channel[i];
|
|
210
|
+
}
|
|
211
|
+
destination[i] += sum * scale;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async toBlob(): Promise<Blob> {
|
|
217
|
+
const wavArrayBuffer = this.audioBufferToWav(this.buffer);
|
|
218
|
+
return new Blob([wavArrayBuffer], { type: "audio/wav" });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async assignToAudioElement(audioElement: HTMLAudioElement): Promise<void> {
|
|
222
|
+
const blob = await this.toBlob();
|
|
223
|
+
audioElement.src = URL.createObjectURL(blob);
|
|
224
|
+
// Optional: audioElement.load();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Simple 16-bit PCM WAV encoder
|
|
228
|
+
// This takes about 0.6 seconds to save 8½ minutes of data.
|
|
229
|
+
private audioBufferToWav(buffer: AudioBuffer): ArrayBuffer {
|
|
230
|
+
console.log("audioBufferToWav()");
|
|
231
|
+
const numChannels = buffer.numberOfChannels;
|
|
232
|
+
const sampleRate = buffer.sampleRate;
|
|
233
|
+
const length = buffer.length * numChannels * 2 + 44;
|
|
234
|
+
const arrayBuffer = new ArrayBuffer(length);
|
|
235
|
+
const view = new DataView(arrayBuffer);
|
|
236
|
+
|
|
237
|
+
const writeString = (offset: number, str: string) => {
|
|
238
|
+
for (let i = 0; i < str.length; i++)
|
|
239
|
+
view.setUint8(offset + i, str.charCodeAt(i));
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
writeString(0, "RIFF");
|
|
243
|
+
view.setUint32(4, 36 + buffer.length * numChannels * 2, true);
|
|
244
|
+
writeString(8, "WAVE");
|
|
245
|
+
writeString(12, "fmt ");
|
|
246
|
+
view.setUint32(16, 16, true);
|
|
247
|
+
view.setUint16(20, 1, true);
|
|
248
|
+
view.setUint16(22, numChannels, true);
|
|
249
|
+
view.setUint32(24, sampleRate, true);
|
|
250
|
+
view.setUint32(28, sampleRate * numChannels * 2, true);
|
|
251
|
+
view.setUint16(32, numChannels * 2, true);
|
|
252
|
+
view.setUint16(34, 16, true);
|
|
253
|
+
writeString(36, "data");
|
|
254
|
+
view.setUint32(40, buffer.length * numChannels * 2, true);
|
|
255
|
+
|
|
256
|
+
let offset = 44;
|
|
257
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
258
|
+
console.log("Wuz lots!");
|
|
259
|
+
const channel = buffer.getChannelData(ch);
|
|
260
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
261
|
+
const sample = Math.max(-1, Math.min(1, channel[i]));
|
|
262
|
+
view.setInt16(
|
|
263
|
+
offset,
|
|
264
|
+
sample < 0 ? sample * 0x8000 : sample * 0x7fff,
|
|
265
|
+
true,
|
|
266
|
+
);
|
|
267
|
+
offset += 2;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return arrayBuffer;
|
|
272
|
+
}
|
|
273
|
+
}
|