@elah/core 0.1.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.
Files changed (153) hide show
  1. package/dist/actions/index.d.ts +3 -0
  2. package/dist/actions/index.js +1 -0
  3. package/dist/actions/splitClipAtPlayhead.d.ts +8 -0
  4. package/dist/actions/splitClipAtPlayhead.js +34 -0
  5. package/dist/actions/types.d.ts +8 -0
  6. package/dist/actions/types.js +1 -0
  7. package/dist/assets/importFiles.d.ts +29 -0
  8. package/dist/assets/importFiles.js +351 -0
  9. package/dist/assets/index.d.ts +10 -0
  10. package/dist/assets/index.js +13 -0
  11. package/dist/assets/store.d.ts +13 -0
  12. package/dist/assets/store.js +20 -0
  13. package/dist/assets/types.d.ts +23 -0
  14. package/dist/assets/types.js +1 -0
  15. package/dist/debug/trace.d.ts +20 -0
  16. package/dist/debug/trace.js +98 -0
  17. package/dist/editor/TimelineEngine.d.ts +65 -0
  18. package/dist/editor/TimelineEngine.js +413 -0
  19. package/dist/editor-context.d.ts +10 -0
  20. package/dist/editor-context.js +10 -0
  21. package/dist/elements/audio.d.ts +13 -0
  22. package/dist/elements/audio.js +4 -0
  23. package/dist/elements/base.d.ts +40 -0
  24. package/dist/elements/base.js +39 -0
  25. package/dist/elements/image.d.ts +13 -0
  26. package/dist/elements/image.js +4 -0
  27. package/dist/elements/text.d.ts +12 -0
  28. package/dist/elements/text.js +8 -0
  29. package/dist/elements/video.d.ts +13 -0
  30. package/dist/elements/video.js +4 -0
  31. package/dist/export/ExportWorker.d.ts +1 -0
  32. package/dist/export/ExportWorker.js +371 -0
  33. package/dist/export/exportVideo.d.ts +3 -0
  34. package/dist/export/exportVideo.js +118 -0
  35. package/dist/export/index.d.ts +2 -0
  36. package/dist/export/index.js +1 -0
  37. package/dist/export/lazyExport.d.ts +4 -0
  38. package/dist/export/lazyExport.js +4 -0
  39. package/dist/export/types.d.ts +37 -0
  40. package/dist/export/types.js +1 -0
  41. package/dist/index.d.ts +48 -0
  42. package/dist/index.js +28 -0
  43. package/dist/media/audio/AudioPlaybackController.d.ts +26 -0
  44. package/dist/media/audio/AudioPlaybackController.js +125 -0
  45. package/dist/media/video/DecoderBackedVideoFrameProvider.d.ts +51 -0
  46. package/dist/media/video/DecoderBackedVideoFrameProvider.js +125 -0
  47. package/dist/media/video/FrameCache.d.ts +28 -0
  48. package/dist/media/video/FrameCache.js +88 -0
  49. package/dist/media/video/StreamingFrameProducer.d.ts +57 -0
  50. package/dist/media/video/StreamingFrameProducer.js +356 -0
  51. package/dist/media/video/VideoDecoderManager.d.ts +59 -0
  52. package/dist/media/video/VideoDecoderManager.js +342 -0
  53. package/dist/media/video/VideoFrameProvider.d.ts +101 -0
  54. package/dist/media/video/VideoFrameProvider.js +257 -0
  55. package/dist/media/video/demuxer/MediabunnyDemuxer.d.ts +23 -0
  56. package/dist/media/video/demuxer/MediabunnyDemuxer.js +88 -0
  57. package/dist/media/video/demuxer/createMediabunnyBackend.d.ts +32 -0
  58. package/dist/media/video/demuxer/createMediabunnyBackend.js +156 -0
  59. package/dist/media/video/index.d.ts +8 -0
  60. package/dist/media/video/index.js +5 -0
  61. package/dist/playback/PlaybackEngine.d.ts +50 -0
  62. package/dist/playback/PlaybackEngine.js +188 -0
  63. package/dist/renderer/gpu/GpuRenderer.d.ts +38 -0
  64. package/dist/renderer/gpu/GpuRenderer.js +208 -0
  65. package/dist/renderer/gpu/RenderGraph.d.ts +10 -0
  66. package/dist/renderer/gpu/RenderGraph.js +80 -0
  67. package/dist/renderer/gpu/ShaderProgram.d.ts +14 -0
  68. package/dist/renderer/gpu/ShaderProgram.js +76 -0
  69. package/dist/renderer/gpu/TexturePool.d.ts +25 -0
  70. package/dist/renderer/gpu/TexturePool.js +93 -0
  71. package/dist/renderer/gpu/VideoTexture.d.ts +13 -0
  72. package/dist/renderer/gpu/VideoTexture.js +54 -0
  73. package/dist/renderer/gpu/WebGLContext.d.ts +28 -0
  74. package/dist/renderer/gpu/WebGLContext.js +102 -0
  75. package/dist/renderer/gpu/debug/DebugGpuRenderer.d.ts +27 -0
  76. package/dist/renderer/gpu/debug/DebugGpuRenderer.js +108 -0
  77. package/dist/renderer/gpu/debug/DebugOverlay.d.ts +17 -0
  78. package/dist/renderer/gpu/debug/DebugOverlay.js +83 -0
  79. package/dist/renderer/gpu/debug/GpuDebugCounters.d.ts +38 -0
  80. package/dist/renderer/gpu/debug/GpuDebugCounters.js +72 -0
  81. package/dist/renderer/gpu/debug/GpuDebugGlobal.d.ts +16 -0
  82. package/dist/renderer/gpu/debug/GpuDebugGlobal.js +14 -0
  83. package/dist/renderer/gpu/debug/GpuRendererDebugPanel.d.ts +31 -0
  84. package/dist/renderer/gpu/debug/GpuRendererDebugPanel.js +128 -0
  85. package/dist/renderer/gpu/debug/RecordingGl.d.ts +88 -0
  86. package/dist/renderer/gpu/debug/RecordingGl.js +214 -0
  87. package/dist/renderer/gpu/debug/playground.d.ts +20 -0
  88. package/dist/renderer/gpu/debug/playground.js +64 -0
  89. package/dist/renderer/gpu/debug/scenarios.d.ts +7 -0
  90. package/dist/renderer/gpu/debug/scenarios.js +145 -0
  91. package/dist/renderer/gpu/debug/types.d.ts +16 -0
  92. package/dist/renderer/gpu/debug/types.js +1 -0
  93. package/dist/renderer/gpu/layers/FrameProbeLayer.d.ts +16 -0
  94. package/dist/renderer/gpu/layers/FrameProbeLayer.js +127 -0
  95. package/dist/renderer/gpu/layers/ImageLayer.d.ts +23 -0
  96. package/dist/renderer/gpu/layers/ImageLayer.js +124 -0
  97. package/dist/renderer/gpu/layers/TestLayer.d.ts +16 -0
  98. package/dist/renderer/gpu/layers/TestLayer.js +109 -0
  99. package/dist/renderer/gpu/layers/TextLayer.d.ts +19 -0
  100. package/dist/renderer/gpu/layers/TextLayer.js +166 -0
  101. package/dist/renderer/gpu/layers/VideoLayer.d.ts +38 -0
  102. package/dist/renderer/gpu/layers/VideoLayer.js +194 -0
  103. package/dist/renderer/gpu/layers/drawRect.d.ts +13 -0
  104. package/dist/renderer/gpu/layers/drawRect.js +55 -0
  105. package/dist/renderer/gpu/layers/objectFit.d.ts +7 -0
  106. package/dist/renderer/gpu/layers/objectFit.js +26 -0
  107. package/dist/renderer/gpu/layers/textLayout.d.ts +47 -0
  108. package/dist/renderer/gpu/layers/textLayout.js +82 -0
  109. package/dist/renderer/gpu/layers/types.d.ts +18 -0
  110. package/dist/renderer/gpu/layers/types.js +1 -0
  111. package/dist/renderer/gpu/shaders/quad.frag.d.ts +1 -0
  112. package/dist/renderer/gpu/shaders/quad.frag.js +14 -0
  113. package/dist/renderer/gpu/shaders/quad.vert.d.ts +1 -0
  114. package/dist/renderer/gpu/shaders/quad.vert.js +28 -0
  115. package/dist/renderer/gpu/types.d.ts +21 -0
  116. package/dist/renderer/gpu/types.js +1 -0
  117. package/dist/renderer/gpu/viewport.d.ts +7 -0
  118. package/dist/renderer/gpu/viewport.js +33 -0
  119. package/dist/renderer/types.d.ts +7 -0
  120. package/dist/renderer/types.js +1 -0
  121. package/dist/resolver/resolveTimeline.d.ts +3 -0
  122. package/dist/resolver/resolveTimeline.js +249 -0
  123. package/dist/resolver/scene.d.ts +54 -0
  124. package/dist/resolver/scene.js +1 -0
  125. package/dist/stores/playback.store.d.ts +50 -0
  126. package/dist/stores/playback.store.js +42 -0
  127. package/dist/stores/selection.store.d.ts +12 -0
  128. package/dist/stores/selection.store.js +19 -0
  129. package/dist/stores/tracks.store.d.ts +19 -0
  130. package/dist/stores/tracks.store.js +27 -0
  131. package/dist/stores/transitions.store.d.ts +9 -0
  132. package/dist/stores/transitions.store.js +7 -0
  133. package/dist/track/track.d.ts +8 -0
  134. package/dist/track/track.js +19 -0
  135. package/dist/types/index.d.ts +117 -0
  136. package/dist/types/index.js +1 -0
  137. package/dist/utils/frames.d.ts +20 -0
  138. package/dist/utils/frames.js +40 -0
  139. package/dist/utils/id.d.ts +1 -0
  140. package/dist/utils/id.js +3 -0
  141. package/dist/utils/snap.d.ts +5 -0
  142. package/dist/utils/snap.js +79 -0
  143. package/dist/visitor/add.d.ts +3 -0
  144. package/dist/visitor/add.js +16 -0
  145. package/dist/visitor/clone.d.ts +3 -0
  146. package/dist/visitor/clone.js +25 -0
  147. package/dist/visitor/remove.d.ts +5 -0
  148. package/dist/visitor/remove.js +29 -0
  149. package/dist/visitor/split.d.ts +3 -0
  150. package/dist/visitor/split.js +31 -0
  151. package/dist/visitor/update.d.ts +4 -0
  152. package/dist/visitor/update.js +31 -0
  153. package/package.json +31 -0
@@ -0,0 +1,127 @@
1
+ import { ShaderProgram } from '../ShaderProgram';
2
+ import { QUAD_FRAG_SRC } from '../shaders/quad.frag';
3
+ import { QUAD_VERT_SRC } from '../shaders/quad.vert';
4
+ import { buildVideoTransformMatrix } from './VideoLayer';
5
+ const PROBE_WIDTH = 640;
6
+ const PROBE_HEIGHT = 360;
7
+ function frameColor(frame) {
8
+ const hue = (frame * 37) % 360;
9
+ return `hsl(${hue}, 70%, 45%)`;
10
+ }
11
+ export class FrameProbeLayer {
12
+ constructor() {
13
+ this._program = null;
14
+ this._vao = null;
15
+ this._gl = null;
16
+ this._resources = new Map();
17
+ }
18
+ acquire(item, ctx) {
19
+ const { gl } = ctx;
20
+ this._gl = gl;
21
+ this._ensurePipeline(gl);
22
+ const texture = gl.createTexture();
23
+ if (!texture) {
24
+ throw new Error('FrameProbeLayer: gl.createTexture() returned null');
25
+ }
26
+ gl.bindTexture(gl.TEXTURE_2D, texture);
27
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
28
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
29
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
30
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
31
+ gl.bindTexture(gl.TEXTURE_2D, null);
32
+ const canvas = document.createElement('canvas');
33
+ canvas.width = PROBE_WIDTH;
34
+ canvas.height = PROBE_HEIGHT;
35
+ const ctx2d = canvas.getContext('2d');
36
+ if (!ctx2d) {
37
+ throw new Error('FrameProbeLayer: 2D context unavailable');
38
+ }
39
+ this._resources.set(item.id, {
40
+ texture,
41
+ canvas,
42
+ ctx2d,
43
+ lastPaintedFrame: -1,
44
+ });
45
+ console.log(`[PROBE-ACQUIRE] clip=${item.id} sourceFrame=${item.sourceFrame}`);
46
+ }
47
+ release(itemId) {
48
+ const res = this._resources.get(itemId);
49
+ if (!res)
50
+ return;
51
+ if (this._gl) {
52
+ this._gl.deleteTexture(res.texture);
53
+ }
54
+ this._resources.delete(itemId);
55
+ console.log(`[PROBE-RELEASE] clip=${itemId}`);
56
+ }
57
+ draw(item, ctx) {
58
+ this._gl = ctx.gl;
59
+ const res = this._resources.get(item.id);
60
+ if (!res || !this._program || !this._vao)
61
+ return;
62
+ const { gl } = ctx;
63
+ const frame = item.sourceFrame;
64
+ if (res.lastPaintedFrame !== frame) {
65
+ this._paint(res, frame);
66
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
67
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, res.canvas);
68
+ gl.bindTexture(gl.TEXTURE_2D, null);
69
+ res.lastPaintedFrame = frame;
70
+ console.log(`[PROBE-UPLOAD] clip=${item.id} frame=${frame}`);
71
+ }
72
+ const opacity = item.opacity ?? 1;
73
+ this._program.use(gl);
74
+ gl.bindVertexArray(this._vao);
75
+ gl.activeTexture(gl.TEXTURE0);
76
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
77
+ this._program.setUniform1i(gl, 'uTexture', 0);
78
+ this._program.setUniform1f(gl, 'uOpacity', opacity);
79
+ this._program.setUniformMatrix3fv(gl, 'uTransform', false, buildVideoTransformMatrix(item, ctx.stage.width, ctx.stage.height));
80
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
81
+ console.log(`[PROBE-DRAW] clip=${item.id} frame=${frame}`);
82
+ gl.bindTexture(gl.TEXTURE_2D, null);
83
+ gl.bindVertexArray(null);
84
+ }
85
+ dispose() {
86
+ this._resources.clear();
87
+ this._program = null;
88
+ this._vao = null;
89
+ this._gl = null;
90
+ }
91
+ notifyContextLost() {
92
+ this._resources.clear();
93
+ this._program = null;
94
+ this._vao = null;
95
+ this._gl = null;
96
+ }
97
+ disposeGL(gl) {
98
+ for (const res of this._resources.values()) {
99
+ gl.deleteTexture(res.texture);
100
+ }
101
+ if (this._vao)
102
+ gl.deleteVertexArray(this._vao);
103
+ if (this._program)
104
+ this._program.dispose(gl);
105
+ this.dispose();
106
+ }
107
+ _paint(res, frame) {
108
+ const { ctx2d } = res;
109
+ ctx2d.fillStyle = frameColor(frame);
110
+ ctx2d.fillRect(0, 0, PROBE_WIDTH, PROBE_HEIGHT);
111
+ ctx2d.fillStyle = '#ffffff';
112
+ ctx2d.font = 'bold 96px monospace';
113
+ ctx2d.textAlign = 'center';
114
+ ctx2d.textBaseline = 'middle';
115
+ ctx2d.fillText(`frame ${frame}`, PROBE_WIDTH / 2, PROBE_HEIGHT / 2);
116
+ }
117
+ _ensurePipeline(gl) {
118
+ if (this._program && this._vao)
119
+ return;
120
+ this._program = ShaderProgram.create(gl, QUAD_VERT_SRC, QUAD_FRAG_SRC);
121
+ const vao = gl.createVertexArray();
122
+ if (!vao) {
123
+ throw new Error('FrameProbeLayer: gl.createVertexArray() returned null');
124
+ }
125
+ this._vao = vao;
126
+ }
127
+ }
@@ -0,0 +1,23 @@
1
+ import type { ActiveImageClip } from '../../../resolver/scene';
2
+ import type { Layer, LayerContext } from './types';
3
+ export interface LoadedImage {
4
+ source: TexImageSource;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export type ImageLoader = (src: string) => Promise<LoadedImage>;
9
+ export declare class ImageLayer implements Layer<ActiveImageClip> {
10
+ private _program;
11
+ private _vao;
12
+ private _gl;
13
+ private readonly _resources;
14
+ private readonly _loadImage;
15
+ constructor(loadImage?: ImageLoader);
16
+ acquire(item: ActiveImageClip, ctx: LayerContext): void;
17
+ release(itemId: string): void;
18
+ draw(item: ActiveImageClip, ctx: LayerContext): void;
19
+ dispose(): void;
20
+ notifyContextLost(): void;
21
+ getTextureCount(): number;
22
+ private _ensurePipeline;
23
+ }
@@ -0,0 +1,124 @@
1
+ import { ShaderProgram } from '../ShaderProgram';
2
+ import { QUAD_FRAG_SRC } from '../shaders/quad.frag';
3
+ import { QUAD_VERT_SRC } from '../shaders/quad.vert';
4
+ import { buildDrawTransformMatrix } from './drawRect';
5
+ const defaultImageLoader = (src) => new Promise((resolve, reject) => {
6
+ const img = document.createElement('img');
7
+ img.onload = () => {
8
+ img.onload = null;
9
+ img.onerror = null;
10
+ resolve({ source: img, width: img.naturalWidth, height: img.naturalHeight });
11
+ };
12
+ img.onerror = () => {
13
+ img.onload = null;
14
+ img.onerror = null;
15
+ reject(new Error(`ImageLayer: failed to load image "${src}"`));
16
+ };
17
+ img.src = src;
18
+ });
19
+ export class ImageLayer {
20
+ constructor(loadImage) {
21
+ this._program = null;
22
+ this._vao = null;
23
+ this._gl = null;
24
+ this._resources = new Map();
25
+ this._loadImage = loadImage ?? defaultImageLoader;
26
+ }
27
+ acquire(item, ctx) {
28
+ const { gl } = ctx;
29
+ this._gl = gl;
30
+ this._ensurePipeline(gl);
31
+ const texture = gl.createTexture();
32
+ if (!texture) {
33
+ throw new Error('ImageLayer: gl.createTexture() returned null');
34
+ }
35
+ gl.bindTexture(gl.TEXTURE_2D, texture);
36
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
37
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
38
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
39
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
40
+ gl.bindTexture(gl.TEXTURE_2D, null);
41
+ const res = { texture, image: null, uploaded: false };
42
+ this._resources.set(item.id, res);
43
+ void this._loadImage(item.src)
44
+ .then((image) => {
45
+ if (this._resources.get(item.id) === res) {
46
+ res.image = image;
47
+ }
48
+ })
49
+ .catch((err) => {
50
+ console.warn(`[ImageLayer] ${err.message}`);
51
+ });
52
+ }
53
+ release(itemId) {
54
+ const res = this._resources.get(itemId);
55
+ if (!res)
56
+ return;
57
+ if (this._gl) {
58
+ this._gl.deleteTexture(res.texture);
59
+ }
60
+ this._resources.delete(itemId);
61
+ }
62
+ draw(item, ctx) {
63
+ this._gl = ctx.gl;
64
+ const res = this._resources.get(item.id);
65
+ if (!res || !this._program || !this._vao)
66
+ return;
67
+ const { gl } = ctx;
68
+ if (res.image && !res.uploaded) {
69
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
70
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
71
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, res.image.source);
72
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
73
+ gl.bindTexture(gl.TEXTURE_2D, null);
74
+ res.uploaded = true;
75
+ }
76
+ if (!res.uploaded || !res.image)
77
+ return;
78
+ const opacity = item.opacity ?? 1;
79
+ this._program.use(gl);
80
+ gl.bindVertexArray(this._vao);
81
+ gl.activeTexture(gl.TEXTURE0);
82
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
83
+ this._program.setUniform1i(gl, 'uTexture', 0);
84
+ this._program.setUniform1f(gl, 'uOpacity', opacity);
85
+ this._program.setUniformMatrix3fv(gl, 'uTransform', false, buildDrawTransformMatrix(item.transform, ctx.stage.width, ctx.stage.height, res.image.width, res.image.height));
86
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
87
+ gl.bindTexture(gl.TEXTURE_2D, null);
88
+ gl.bindVertexArray(null);
89
+ }
90
+ dispose() {
91
+ if (this._gl) {
92
+ for (const res of this._resources.values()) {
93
+ this._gl.deleteTexture(res.texture);
94
+ }
95
+ if (this._vao)
96
+ this._gl.deleteVertexArray(this._vao);
97
+ if (this._program)
98
+ this._program.dispose(this._gl);
99
+ }
100
+ this._resources.clear();
101
+ this._program = null;
102
+ this._vao = null;
103
+ this._gl = null;
104
+ }
105
+ notifyContextLost() {
106
+ this._resources.clear();
107
+ this._program = null;
108
+ this._vao = null;
109
+ this._gl = null;
110
+ }
111
+ getTextureCount() {
112
+ return this._resources.size;
113
+ }
114
+ _ensurePipeline(gl) {
115
+ if (this._program && this._vao)
116
+ return;
117
+ this._program = ShaderProgram.create(gl, QUAD_VERT_SRC, QUAD_FRAG_SRC);
118
+ const vao = gl.createVertexArray();
119
+ if (!vao) {
120
+ throw new Error('ImageLayer: gl.createVertexArray() returned null');
121
+ }
122
+ this._vao = vao;
123
+ }
124
+ }
@@ -0,0 +1,16 @@
1
+ import type { DebugRenderItem } from '../debug/types';
2
+ import type { Layer, LayerContext } from './types';
3
+ export declare function buildTransformMatrix(item: DebugRenderItem, stageWidth: number, stageHeight: number): Float32Array;
4
+ export declare class TestLayer implements Layer<DebugRenderItem> {
5
+ private _program;
6
+ private _vao;
7
+ private _gl;
8
+ private readonly _resources;
9
+ acquire(item: DebugRenderItem, ctx: LayerContext): void;
10
+ release(itemId: string): void;
11
+ draw(item: DebugRenderItem, ctx: LayerContext): void;
12
+ dispose(): void;
13
+ notifyContextLost(): void;
14
+ disposeGL(gl: WebGL2RenderingContext): void;
15
+ private _ensurePipeline;
16
+ }
@@ -0,0 +1,109 @@
1
+ import { ShaderProgram } from '../ShaderProgram';
2
+ import { QUAD_FRAG_SRC } from '../shaders/quad.frag';
3
+ import { QUAD_VERT_SRC } from '../shaders/quad.vert';
4
+ export function buildTransformMatrix(item, stageWidth, stageHeight) {
5
+ const xs = item.x / stageWidth;
6
+ const ys = item.y / stageHeight;
7
+ const ws = item.width / stageWidth;
8
+ const hs = item.height / stageHeight;
9
+ const rotation = item.rotation ?? 0;
10
+ const sc = Math.cos(rotation);
11
+ const ss = Math.sin(rotation);
12
+ const tx = 2 * (xs + ws / 2 - 0.5 * sc * ws + 0.5 * ss * hs) - 1;
13
+ const ty = 2 * (ys + hs / 2 - 0.5 * ss * ws - 0.5 * sc * hs) - 1;
14
+ return new Float32Array([
15
+ 2 * sc * ws, 2 * ss * ws, 0,
16
+ -2 * ss * hs, 2 * sc * hs, 0,
17
+ tx, ty, 1,
18
+ ]);
19
+ }
20
+ export class TestLayer {
21
+ constructor() {
22
+ this._program = null;
23
+ this._vao = null;
24
+ this._gl = null;
25
+ this._resources = new Map();
26
+ }
27
+ acquire(item, ctx) {
28
+ const { gl } = ctx;
29
+ this._gl = gl;
30
+ this._ensurePipeline(gl);
31
+ const texture = gl.createTexture();
32
+ if (!texture) {
33
+ throw new Error('TestLayer: gl.createTexture() returned null');
34
+ }
35
+ gl.bindTexture(gl.TEXTURE_2D, texture);
36
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
37
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
38
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
39
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
40
+ const [r, g, b, a] = item.color;
41
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
42
+ Math.round(r * 255),
43
+ Math.round(g * 255),
44
+ Math.round(b * 255),
45
+ Math.round(a * 255),
46
+ ]));
47
+ gl.bindTexture(gl.TEXTURE_2D, null);
48
+ this._resources.set(item.id, { texture, color: item.color });
49
+ }
50
+ release(itemId) {
51
+ const res = this._resources.get(itemId);
52
+ if (!res)
53
+ return;
54
+ if (this._gl) {
55
+ this._gl.deleteTexture(res.texture);
56
+ }
57
+ this._resources.delete(itemId);
58
+ }
59
+ draw(item, ctx) {
60
+ this._gl = ctx.gl;
61
+ const res = this._resources.get(item.id);
62
+ if (!res || !this._program || !this._vao)
63
+ return;
64
+ const { gl } = ctx;
65
+ const opacity = item.opacity ?? 1;
66
+ this._program.use(gl);
67
+ gl.bindVertexArray(this._vao);
68
+ gl.activeTexture(gl.TEXTURE0);
69
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
70
+ this._program.setUniform1i(gl, 'uTexture', 0);
71
+ this._program.setUniform1f(gl, 'uOpacity', opacity);
72
+ this._program.setUniformMatrix3fv(gl, 'uTransform', false, buildTransformMatrix(item, ctx.stage.width, ctx.stage.height));
73
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
74
+ gl.bindTexture(gl.TEXTURE_2D, null);
75
+ gl.bindVertexArray(null);
76
+ }
77
+ dispose() {
78
+ this._resources.clear();
79
+ this._program = null;
80
+ this._vao = null;
81
+ this._gl = null;
82
+ }
83
+ notifyContextLost() {
84
+ this._resources.clear();
85
+ this._program = null;
86
+ this._vao = null;
87
+ this._gl = null;
88
+ }
89
+ disposeGL(gl) {
90
+ for (const res of this._resources.values()) {
91
+ gl.deleteTexture(res.texture);
92
+ }
93
+ if (this._vao)
94
+ gl.deleteVertexArray(this._vao);
95
+ if (this._program)
96
+ this._program.dispose(gl);
97
+ this.dispose();
98
+ }
99
+ _ensurePipeline(gl) {
100
+ if (this._program && this._vao)
101
+ return;
102
+ this._program = ShaderProgram.create(gl, QUAD_VERT_SRC, QUAD_FRAG_SRC);
103
+ const vao = gl.createVertexArray();
104
+ if (!vao) {
105
+ throw new Error('TestLayer: gl.createVertexArray() returned null');
106
+ }
107
+ this._vao = vao;
108
+ }
109
+ }
@@ -0,0 +1,19 @@
1
+ import type { ActiveTextClip } from '../../../resolver/scene';
2
+ import type { Layer, LayerContext } from './types';
3
+ export type TextCanvasFactory = () => HTMLCanvasElement;
4
+ export declare class TextLayer implements Layer<ActiveTextClip> {
5
+ private _program;
6
+ private _vao;
7
+ private _gl;
8
+ private readonly _resources;
9
+ private readonly _createCanvas;
10
+ constructor(createCanvas?: TextCanvasFactory);
11
+ acquire(item: ActiveTextClip, ctx: LayerContext): void;
12
+ release(itemId: string): void;
13
+ draw(item: ActiveTextClip, ctx: LayerContext): void;
14
+ dispose(): void;
15
+ notifyContextLost(): void;
16
+ getTextureCount(): number;
17
+ private _paint;
18
+ private _ensurePipeline;
19
+ }
@@ -0,0 +1,166 @@
1
+ import { ShaderProgram } from '../ShaderProgram';
2
+ import { QUAD_FRAG_SRC } from '../shaders/quad.frag';
3
+ import { QUAD_VERT_SRC } from '../shaders/quad.vert';
4
+ import { computeTextLayout } from './textLayout';
5
+ const FULL_STAGE_MAT3 = new Float32Array([2, 0, 0, 0, 2, 0, -1, -1, 1]);
6
+ function buildTextTransformMatrix(transform, stage) {
7
+ if (!transform || transform.rotation === 0) {
8
+ return FULL_STAGE_MAT3;
9
+ }
10
+ const a = Math.cos(transform.rotation);
11
+ const b = Math.sin(transform.rotation);
12
+ const px = transform.x;
13
+ const py = transform.y;
14
+ const wOverH = stage.width / stage.height;
15
+ const hOverW = stage.height / stage.width;
16
+ const txx = 2 * (px - a * px + b * hOverW * py) - 1;
17
+ const tyy = 2 * (py - b * wOverH * px - a * py) - 1;
18
+ return new Float32Array([
19
+ 2 * a, 2 * b * wOverH, 0,
20
+ -2 * b * hOverW, 2 * a, 0,
21
+ txx, tyy, 1,
22
+ ]);
23
+ }
24
+ function paintSignature(item, stage) {
25
+ return JSON.stringify([
26
+ item.content,
27
+ item.fontSize,
28
+ item.color,
29
+ item.fontFamily,
30
+ item.fontWeight,
31
+ item.textAlign,
32
+ item.transform?.x,
33
+ item.transform?.y,
34
+ item.transform?.scale,
35
+ stage.width,
36
+ stage.height,
37
+ ]);
38
+ }
39
+ export class TextLayer {
40
+ constructor(createCanvas) {
41
+ this._program = null;
42
+ this._vao = null;
43
+ this._gl = null;
44
+ this._resources = new Map();
45
+ this._createCanvas = createCanvas ?? (() => document.createElement('canvas'));
46
+ }
47
+ acquire(item, ctx) {
48
+ const { gl } = ctx;
49
+ this._gl = gl;
50
+ this._ensurePipeline(gl);
51
+ const texture = gl.createTexture();
52
+ if (!texture) {
53
+ throw new Error('TextLayer: gl.createTexture() returned null');
54
+ }
55
+ gl.bindTexture(gl.TEXTURE_2D, texture);
56
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
57
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
58
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
59
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
60
+ gl.bindTexture(gl.TEXTURE_2D, null);
61
+ const canvas = this._createCanvas();
62
+ canvas.width = ctx.stage.width;
63
+ canvas.height = ctx.stage.height;
64
+ const ctx2d = canvas.getContext('2d');
65
+ if (!ctx2d) {
66
+ throw new Error('TextLayer: 2D context unavailable');
67
+ }
68
+ this._resources.set(item.id, {
69
+ texture,
70
+ canvas,
71
+ ctx2d,
72
+ width: canvas.width,
73
+ height: canvas.height,
74
+ lastSignature: '',
75
+ });
76
+ }
77
+ release(itemId) {
78
+ const res = this._resources.get(itemId);
79
+ if (!res)
80
+ return;
81
+ if (this._gl) {
82
+ this._gl.deleteTexture(res.texture);
83
+ }
84
+ this._resources.delete(itemId);
85
+ }
86
+ draw(item, ctx) {
87
+ this._gl = ctx.gl;
88
+ const res = this._resources.get(item.id);
89
+ if (!res || !this._program || !this._vao)
90
+ return;
91
+ const { gl } = ctx;
92
+ if (res.width !== ctx.stage.width || res.height !== ctx.stage.height) {
93
+ res.canvas.width = ctx.stage.width;
94
+ res.canvas.height = ctx.stage.height;
95
+ res.width = ctx.stage.width;
96
+ res.height = ctx.stage.height;
97
+ res.lastSignature = '';
98
+ }
99
+ const sig = paintSignature(item, ctx.stage);
100
+ if (res.lastSignature !== sig) {
101
+ this._paint(res, item, ctx.stage);
102
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
103
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
104
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, res.canvas);
105
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
106
+ gl.bindTexture(gl.TEXTURE_2D, null);
107
+ res.lastSignature = sig;
108
+ }
109
+ const opacity = item.opacity ?? 1;
110
+ this._program.use(gl);
111
+ gl.bindVertexArray(this._vao);
112
+ gl.activeTexture(gl.TEXTURE0);
113
+ gl.bindTexture(gl.TEXTURE_2D, res.texture);
114
+ this._program.setUniform1i(gl, 'uTexture', 0);
115
+ this._program.setUniform1f(gl, 'uOpacity', opacity);
116
+ this._program.setUniformMatrix3fv(gl, 'uTransform', false, buildTextTransformMatrix(item.transform, ctx.stage));
117
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
118
+ gl.bindTexture(gl.TEXTURE_2D, null);
119
+ gl.bindVertexArray(null);
120
+ }
121
+ dispose() {
122
+ if (this._gl) {
123
+ for (const res of this._resources.values()) {
124
+ this._gl.deleteTexture(res.texture);
125
+ }
126
+ if (this._vao)
127
+ this._gl.deleteVertexArray(this._vao);
128
+ if (this._program)
129
+ this._program.dispose(this._gl);
130
+ }
131
+ this._resources.clear();
132
+ this._program = null;
133
+ this._vao = null;
134
+ this._gl = null;
135
+ }
136
+ notifyContextLost() {
137
+ this._resources.clear();
138
+ this._program = null;
139
+ this._vao = null;
140
+ this._gl = null;
141
+ }
142
+ getTextureCount() {
143
+ return this._resources.size;
144
+ }
145
+ _paint(res, item, stage) {
146
+ const { ctx2d } = res;
147
+ ctx2d.clearRect(0, 0, stage.width, stage.height);
148
+ const layout = computeTextLayout(ctx2d, item, stage);
149
+ ctx2d.fillStyle = layout.style.color;
150
+ ctx2d.textBaseline = 'middle';
151
+ ctx2d.textAlign = layout.style.textAlign;
152
+ layout.lines.forEach((line, i) => {
153
+ ctx2d.fillText(line, layout.anchorX, layout.firstLineY + i * layout.lineAdvance);
154
+ });
155
+ }
156
+ _ensurePipeline(gl) {
157
+ if (this._program && this._vao)
158
+ return;
159
+ this._program = ShaderProgram.create(gl, QUAD_VERT_SRC, QUAD_FRAG_SRC);
160
+ const vao = gl.createVertexArray();
161
+ if (!vao) {
162
+ throw new Error('TextLayer: gl.createVertexArray() returned null');
163
+ }
164
+ this._vao = vao;
165
+ }
166
+ }
@@ -0,0 +1,38 @@
1
+ import type { ActiveVideoClip } from '../../../resolver/scene';
2
+ import type { TexturePool } from '../TexturePool';
3
+ import { type VideoFrameProvider, type VideoFrameProviderDeps } from '../../../media/video';
4
+ import { type DrawRect } from './drawRect';
5
+ import type { Layer, LayerContext } from './types';
6
+ export { buildTransformMatrixFromRect, resolveTransformRect, resolveDrawRect, buildDrawTransformMatrix, } from './drawRect';
7
+ export type { DrawRect } from './drawRect';
8
+ export type VideoDrawRect = DrawRect;
9
+ export declare function buildVideoTransformMatrix(item: ActiveVideoClip, stageWidth: number, stageHeight: number, contentWidth?: number, contentHeight?: number): Float32Array;
10
+ export type VideoFrameProviderFactory = (src: string) => VideoFrameProvider;
11
+ export declare class VideoLayer implements Layer<ActiveVideoClip> {
12
+ private readonly _pool;
13
+ private readonly _providerFactory;
14
+ private readonly _deps;
15
+ private _program;
16
+ private _vao;
17
+ private _gl;
18
+ private readonly _providers;
19
+ private readonly _textures;
20
+ private readonly _srcByItemId;
21
+ private readonly _contentSizeByItemId;
22
+ private readonly _lastLoggedSourceFrameByItemId;
23
+ private _holdoverTexture;
24
+ constructor(pool: TexturePool, providerFactoryOrDeps?: VideoFrameProviderFactory | VideoFrameProviderDeps);
25
+ acquire(item: ActiveVideoClip, ctx: LayerContext): void;
26
+ release(itemId: string): void;
27
+ draw(item: ActiveVideoClip, ctx: LayerContext): void;
28
+ dispose(): void;
29
+ notifyContextLost(): void;
30
+ disposeGL(gl: WebGL2RenderingContext): void;
31
+ getProviderForItemId(itemId: string): VideoFrameProvider | undefined;
32
+ getProviderForSrc(src: string): VideoFrameProvider | undefined;
33
+ getProviderRefCount(itemId: string): number;
34
+ getTextureCount(): number;
35
+ getProviderCount(): number;
36
+ getDecoderStates(): Record<string, string>;
37
+ private _ensurePipeline;
38
+ }