@viji-dev/sdk 1.0.0 → 1.0.1

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 (77) hide show
  1. package/README.md +155 -60
  2. package/bin/viji.js +9 -29
  3. package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
  4. package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
  5. package/dist/assets/core-CiQx3w0t.js +12 -0
  6. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  7. package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
  8. package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
  9. package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
  10. package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
  11. package/dist/assets/glsl-DMyvO4G4.js +1 -0
  12. package/dist/assets/index-BhFxsauQ.js +215 -0
  13. package/dist/assets/index-BqhVeA7U.css +1 -0
  14. package/dist/assets/index-T4TOjvD0.js +1 -0
  15. package/dist/assets/index-Wz9WqGqz.js +52 -0
  16. package/dist/assets/index-t24aGwla.js +1 -0
  17. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  18. package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
  19. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  20. package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
  21. package/{index.html → dist/index.html} +2 -1
  22. package/package.json +31 -35
  23. package/src/cli/commands/build.js +50 -99
  24. package/src/cli/commands/create.js +32 -47
  25. package/src/cli/commands/dev.js +30 -97
  26. package/src/cli/server/dev-server.js +233 -0
  27. package/src/cli/server/scene-scanner.js +93 -0
  28. package/src/cli/server/vite-scene-plugin.d.ts +2 -0
  29. package/src/cli/server/vite-scene-plugin.js +134 -0
  30. package/src/cli/utils/cli-utils.js +29 -139
  31. package/src/cli/utils/scene-compiler.js +10 -17
  32. package/src/templates/scene-templates.js +85 -0
  33. package/.gitignore +0 -29
  34. package/eslint.config.js +0 -37
  35. package/postcss.config.js +0 -6
  36. package/scenes/audio-visualizer/main.js +0 -287
  37. package/scenes/core-demo/main.js +0 -532
  38. package/scenes/demo-scene/main.js +0 -619
  39. package/scenes/global.d.ts +0 -15
  40. package/scenes/particle-system/main.js +0 -349
  41. package/scenes/tsconfig.json +0 -12
  42. package/scenes/video-mirror/main.ts +0 -436
  43. package/src/App.css +0 -42
  44. package/src/App.tsx +0 -279
  45. package/src/cli/commands/init.js +0 -262
  46. package/src/components/SDKPage.tsx +0 -337
  47. package/src/components/core/CoreContainer.tsx +0 -126
  48. package/src/components/ui/DeviceSelectionList.tsx +0 -137
  49. package/src/components/ui/FPSCounter.tsx +0 -78
  50. package/src/components/ui/FileDropzonePanel.tsx +0 -120
  51. package/src/components/ui/FileListPanel.tsx +0 -285
  52. package/src/components/ui/InputExpansionPanel.tsx +0 -31
  53. package/src/components/ui/MediaPlayerControls.tsx +0 -191
  54. package/src/components/ui/MenuContainer.tsx +0 -71
  55. package/src/components/ui/ParametersMenu.tsx +0 -797
  56. package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
  57. package/src/components/ui/QuickInputControls.tsx +0 -542
  58. package/src/components/ui/SDKMenuSystem.tsx +0 -96
  59. package/src/components/ui/SettingsMenu.tsx +0 -346
  60. package/src/components/ui/SimpleInputControls.tsx +0 -137
  61. package/src/index.css +0 -68
  62. package/src/main.tsx +0 -10
  63. package/src/scenes-hmr.ts +0 -158
  64. package/src/services/project-filesystem.ts +0 -436
  65. package/src/stores/scene-player/index.ts +0 -3
  66. package/src/stores/scene-player/input-manager.store.ts +0 -1045
  67. package/src/stores/scene-player/scene-session.store.ts +0 -659
  68. package/src/styles/globals.css +0 -111
  69. package/src/templates/minimal-template.js +0 -11
  70. package/src/utils/debounce.js +0 -34
  71. package/src/vite-env.d.ts +0 -1
  72. package/tailwind.config.js +0 -18
  73. package/tsconfig.app.json +0 -27
  74. package/tsconfig.json +0 -27
  75. package/tsconfig.node.json +0 -27
  76. package/vite.config.ts +0 -54
  77. /package/{public → dist}/favicon.png +0 -0
@@ -1,532 +0,0 @@
1
-
2
- // Scene Code - Parameter Object API
3
- console.log('🎯 CORE DEMO SCENE LOADED');
4
-
5
- // Define parameters using helper functions (returns parameter objects)
6
- const primaryColor = viji.color('#ff6b6b', {
7
- label: 'Primary Color',
8
- description: 'Main color for the animated shapes',
9
- group: 'colors'
10
- });
11
-
12
- const secondaryColor = viji.color('#4ecdc4', {
13
- label: 'Secondary Color',
14
- description: 'Secondary color for gradient effects',
15
- group: 'colors'
16
- });
17
-
18
- const shapeSize = viji.slider(50, {
19
- min: 10,
20
- max: 150,
21
- step: 5,
22
- label: 'Shape Size',
23
- description: 'Size of the animated shapes',
24
- group: 'shape'
25
- });
26
-
27
- const animationSpeed = viji.slider(1.0, {
28
- min: 0.1,
29
- max: 3.0,
30
- step: 0.1,
31
- label: 'Animation Speed',
32
- description: 'Speed of the animation loop',
33
- group: 'speed'
34
- });
35
-
36
- const useGradient = viji.toggle(true, {
37
- label: 'Use Gradient',
38
- description: 'Enable gradient fill for shapes',
39
- group: 'colors'
40
- });
41
-
42
- const shapeType = viji.select('circle', {
43
- options: ['circle', 'square', 'triangle'],
44
- label: 'Shape Type',
45
- description: 'Type of shape to render',
46
- group: 'shape'
47
- });
48
-
49
- const particleCount = viji.number(5, {
50
- min: 1,
51
- max: 20,
52
- step: 1,
53
- label: 'Particle Count',
54
- description: 'Number of particles to render',
55
- group: 'shape'
56
- });
57
-
58
- const sceneTitle = viji.text('Viji Core Demo', {
59
- label: 'Scene Title',
60
- description: 'Title displayed in the scene',
61
- group: 'text',
62
- maxLength: 50
63
- });
64
-
65
- const rotationSpeed = viji.slider(0, {
66
- min: -5,
67
- max: 5,
68
- step: 0.1,
69
- label: 'Rotation Speed',
70
- description: 'Rotation speed for shapes',
71
- group: 'speed'
72
- });
73
-
74
- const opacity = viji.slider(0.8, {
75
- min: 0,
76
- max: 1,
77
- step: 0.05,
78
- label: 'Opacity',
79
- description: 'Overall opacity of the scene',
80
- group: 'colors'
81
- });
82
-
83
- const enableTrails = viji.toggle(false, {
84
- label: 'Enable Trails',
85
- description: 'Show trails behind moving shapes'
86
- });
87
-
88
- // Interaction Parameters
89
- const mouseAttraction = viji.toggle(true, {
90
- label: 'Mouse Attraction',
91
- description: 'Particles are attracted to mouse cursor',
92
- group: 'interaction',
93
- category: 'interaction'
94
- });
95
-
96
- const attractionStrength = viji.slider(0.3, {
97
- min: 0,
98
- max: 1,
99
- step: 0.1,
100
- label: 'Attraction Strength',
101
- description: 'How strongly particles are attracted to mouse',
102
- group: 'interaction',
103
- category: 'interaction'
104
- });
105
-
106
- const keyboardControl = viji.toggle(true, {
107
- label: 'Keyboard Control',
108
- description: 'Use WASD keys to move particles',
109
- group: 'interaction',
110
- category: 'interaction'
111
- });
112
-
113
- const touchScale = viji.slider(1.0, {
114
- min: 0.5,
115
- max: 2.0,
116
- step: 0.1,
117
- label: 'Touch Scale Factor',
118
- description: 'Scale factor for touch interactions',
119
- group: 'interaction',
120
- category: 'interaction'
121
- });
122
-
123
- const showInteractionInfo = viji.toggle(true, {
124
- label: 'Show Interaction Info',
125
- description: 'Display current interaction state',
126
- group: 'interaction',
127
- category: 'interaction'
128
- });
129
-
130
- // Audio Parameters
131
- const audioReactive = viji.toggle(false, {
132
- label: 'Audio Reactive',
133
- description: 'Make particles react to audio input',
134
- group: 'audio',
135
- category: 'audio'
136
- });
137
-
138
- const volumeSensitivity = viji.slider(1.0, {
139
- min: 0.1,
140
- max: 5.0,
141
- step: 0.1,
142
- label: 'Volume Sensitivity',
143
- description: 'How sensitive particles are to volume changes',
144
- group: 'audio',
145
- category: 'audio'
146
- });
147
-
148
- const bassReactivity = viji.slider(1.0, {
149
- min: 0,
150
- max: 3.0,
151
- step: 0.1,
152
- label: 'Bass Reactivity',
153
- description: 'How much particles react to bass frequencies',
154
- group: 'audio',
155
- category: 'audio'
156
- });
157
-
158
- const trebleReactivity = viji.slider(0.5, {
159
- min: 0,
160
- max: 2.0,
161
- step: 0.1,
162
- label: 'Treble Reactivity',
163
- description: 'How much particles react to treble frequencies',
164
- group: 'audio',
165
- category: 'audio'
166
- });
167
-
168
- const showAudioViz = viji.toggle(true, {
169
- label: 'Show Audio Visualization',
170
- description: 'Display real-time audio analysis on canvas',
171
- group: 'audio',
172
- category: 'audio'
173
- });
174
-
175
- // Video Parameters
176
- const videoReactive = viji.toggle(false, {
177
- label: 'Video Reactive',
178
- description: 'Make particles react to video input',
179
- group: 'video',
180
- category: 'video'
181
- });
182
-
183
- const motionSensitivity = viji.slider(1.0, {
184
- min: 0.1,
185
- max: 3.0,
186
- step: 0.1,
187
- label: 'Motion Sensitivity',
188
- description: 'How sensitive particles are to video motion',
189
- group: 'video',
190
- category: 'video'
191
- });
192
-
193
- const brightnessReactivity = viji.slider(0.5, {
194
- min: 0,
195
- max: 2.0,
196
- step: 0.1,
197
- label: 'Brightness Reactivity',
198
- description: 'How much particles react to video brightness',
199
- group: 'video',
200
- category: 'video'
201
- });
202
-
203
- const showVideoOverlay = viji.toggle(true, {
204
- label: 'Show Video Overlay',
205
- description: 'Display current video frame on canvas',
206
- group: 'video',
207
- category: 'video'
208
- });
209
-
210
- // Render function using parameter object API with interactions, audio, and video
211
- function render(viji) {
212
- const ctx = viji.useContext('2d');
213
-
214
- // Get interaction state
215
- const mouse = viji.mouse;
216
- const keyboard = viji.keyboard;
217
- const touch = viji.touches;
218
-
219
- // Get audio state
220
- const audio = viji.audio;
221
-
222
- // Get video state
223
- const video = viji.video;
224
-
225
- // Clear canvas with trails effect if enabled
226
- if (enableTrails.value) {
227
- ctx.fillStyle = 'rgba(0, 0, 0, ' + (0.1 * opacity.value) + ')';
228
- ctx.fillRect(0, 0, viji.width, viji.height);
229
- } else {
230
- ctx.fillStyle = '#2c3e35';
231
- ctx.fillRect(0, 0, viji.width, viji.height);
232
- }
233
-
234
- // Draw title using parameter object
235
- ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
236
- ctx.font = '20px Arial';
237
- ctx.textAlign = 'center';
238
- ctx.fillText(sceneTitle.value, viji.width / 2, 30);
239
-
240
- // Keyboard offset for particle center
241
- let keyboardOffsetX = 0;
242
- let keyboardOffsetY = 0;
243
-
244
- if (keyboardControl.value) {
245
- const keySpeed = 2;
246
- if (keyboard.isPressed('w') || keyboard.isPressed('W')) keyboardOffsetY -= keySpeed;
247
- if (keyboard.isPressed('s') || keyboard.isPressed('S')) keyboardOffsetY += keySpeed;
248
- if (keyboard.isPressed('a') || keyboard.isPressed('A')) keyboardOffsetX -= keySpeed;
249
- if (keyboard.isPressed('d') || keyboard.isPressed('D')) keyboardOffsetX += keySpeed;
250
- }
251
-
252
- // Animation calculations using parameter objects
253
- let centerX = viji.width / 2 + keyboardOffsetX;
254
- let centerY = viji.height / 2 + keyboardOffsetY;
255
- const time = viji.time * animationSpeed.value;
256
-
257
- // Audio reactive calculations
258
- let audioScale = 1;
259
- let audioColorShift = 0;
260
- if (audioReactive.value && audio.isConnected) {
261
- // Scale based on volume (RMS)
262
- audioScale = 1 + (audio.volume.rms * volumeSensitivity.value);
263
-
264
- // Color shift based on bass vs treble
265
- const bassEnergy = (audio.bands.bass + audio.bands.subBass) / 2;
266
- const trebleEnergy = (audio.bands.treble + audio.bands.presence) / 2;
267
- audioColorShift = (bassEnergy * bassReactivity.value) - (trebleEnergy * trebleReactivity.value);
268
- }
269
-
270
- // Video reactive calculations
271
- let videoScale = 1;
272
- let videoBrightness = 0;
273
- if (videoReactive.value && video.isConnected) {
274
- // Simple motion detection (placeholder - would use actual frame analysis)
275
- const motionEnergy = Math.sin(time * 2) * 0.5 + 0.5; // Simulate motion
276
- videoScale = 1 + (motionEnergy * motionSensitivity.value * 0.3);
277
-
278
- // Brightness-based effects
279
- const brightness = Math.sin(time * 1.5) * 0.5 + 0.5; // Simulate brightness
280
- videoBrightness = brightness * brightnessReactivity.value;
281
- }
282
-
283
- // Touch interaction - use primary touch for center offset
284
- if (touch.points.length > 0) {
285
- const primaryTouch = touch.points[0];
286
- centerX = primaryTouch.x * touchScale.value;
287
- centerY = primaryTouch.y * touchScale.value;
288
- }
289
-
290
- // Draw particles
291
- for (let i = 0; i < particleCount.value; i++) {
292
- const angle = (i / particleCount.value) * Math.PI * 2 + time;
293
- const radius = Math.min(viji.width, viji.height) * 0.3;
294
-
295
- let x = centerX + Math.cos(angle) * radius;
296
- let y = centerY + Math.sin(angle) * radius;
297
-
298
- // Mouse attraction
299
- if (mouseAttraction.value && mouse.isInCanvas) {
300
- const mouseX = mouse.x;
301
- const mouseY = mouse.y;
302
- const distToMouse = Math.sqrt((x - mouseX) ** 2 + (y - mouseY) ** 2);
303
-
304
- if (distToMouse > 0) {
305
- const attraction = attractionStrength.value * (1 / (distToMouse * 0.01 + 1));
306
- x += (mouseX - x) * attraction;
307
- y += (mouseY - y) * attraction;
308
- }
309
- }
310
-
311
- // Apply rotation using parameter object
312
- const rotation = time * rotationSpeed.value;
313
-
314
- ctx.save();
315
- ctx.translate(x, y);
316
- ctx.rotate(rotation);
317
-
318
- // Set opacity using parameter object
319
- ctx.globalAlpha = opacity.value;
320
-
321
- let scaledSize = shapeSize.value * Math.min(viji.width, viji.height) / 1000;
322
-
323
- // Apply audio scaling
324
- if (audioReactive.value && audio.isConnected) {
325
- scaledSize *= audioScale;
326
- }
327
-
328
- // Apply video scaling
329
- if (videoReactive.value && video.isConnected) {
330
- scaledSize *= videoScale;
331
- }
332
-
333
- // Create gradient or solid fill using parameter objects
334
- if (useGradient.value) {
335
- const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, scaledSize / 2);
336
- gradient.addColorStop(0, primaryColor.value);
337
- gradient.addColorStop(1, secondaryColor.value);
338
- ctx.fillStyle = gradient;
339
- } else {
340
- ctx.fillStyle = primaryColor.value;
341
- }
342
-
343
- // Draw shape based on type using parameter object
344
- ctx.beginPath();
345
- switch (shapeType.value) {
346
- case 'circle':
347
- ctx.arc(0, 0, scaledSize / 2, 0, Math.PI * 2);
348
- break;
349
- case 'square':
350
- const half = scaledSize / 2;
351
- ctx.rect(-half, -half, scaledSize, scaledSize);
352
- break;
353
- case 'triangle':
354
- const size = scaledSize;
355
- ctx.moveTo(0, -size / 2);
356
- ctx.lineTo(-size / 2, size / 2);
357
- ctx.lineTo(size / 2, size / 2);
358
- ctx.closePath();
359
- break;
360
- }
361
- ctx.fill();
362
-
363
- ctx.restore();
364
- }
365
-
366
- // Draw debug info showing parameter object properties
367
- ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
368
- ctx.font = '12px monospace';
369
- ctx.textAlign = 'left';
370
- ctx.fillText('Frame: ' + viji.frameCount, 10, viji.height - 100);
371
- ctx.fillText('Time: ' + viji.time.toFixed(2) + 's', 10, viji.height - 85);
372
- ctx.fillText('FPS: ' + viji.fps, 10, viji.height - 70);
373
- ctx.fillText('Shape Size: ' + shapeSize.value + ' (min: ' + shapeSize.min + ', max: ' + shapeSize.max + ')', 10, viji.height - 55);
374
- ctx.fillText('Animation Speed: ' + animationSpeed.value.toFixed(1), 10, viji.height - 40);
375
- ctx.fillText('Rotation: ' + rotationSpeed.value.toFixed(1), 10, viji.height - 25);
376
- ctx.fillText('Particle Count: ' + particleCount.value + ' (max: ' + particleCount.max + ')', 10, viji.height - 10);
377
-
378
- // Draw interaction info
379
- if (showInteractionInfo.value) {
380
- ctx.fillStyle = 'rgba(255, 255, 0, 0.8)';
381
- ctx.font = '12px monospace';
382
- ctx.textAlign = 'right';
383
-
384
- let infoY = viji.height - 120;
385
-
386
- // Mouse info
387
- ctx.fillText('INTERACTIONS:', viji.width - 10, infoY);
388
- infoY += 15;
389
- ctx.fillText('Mouse: ' + (mouse.isInCanvas ? `(${Math.round(mouse.x)}, ${Math.round(mouse.y)})` : 'outside'), viji.width - 10, infoY);
390
- infoY += 15;
391
- ctx.fillText('Mouse Buttons: ' + (mouse.leftButton ? 'L' : '-') + (mouse.rightButton ? 'R' : '-') + (mouse.middleButton ? 'M' : '-'), viji.width - 10, infoY);
392
- infoY += 15;
393
-
394
- // Keyboard info
395
- const activeKeys = Array.from(keyboard.activeKeys);
396
- ctx.fillText('Keys: ' + (activeKeys.length > 0 ? activeKeys.slice(0, 5).join(', ') : 'none'), viji.width - 10, infoY);
397
- infoY += 15;
398
-
399
- // Touch info
400
- ctx.fillText('Touches: ' + touch.points.length, viji.width - 10, infoY);
401
- infoY += 15;
402
-
403
- if (touch.points.length > 0) {
404
- const touch0 = touch.points[0];
405
- ctx.fillText('Touch 0: (' + Math.round(touch0.x) + ', ' + Math.round(touch0.y) + ')', viji.width - 10, infoY);
406
- }
407
- }
408
-
409
- // Draw mouse cursor indicator
410
- if (mouseAttraction.value && mouse.isInCanvas) {
411
- ctx.strokeStyle = 'rgba(255, 255, 0, 0.8)';
412
- ctx.lineWidth = 2;
413
- ctx.setLineDash([5, 5]);
414
- ctx.beginPath();
415
- ctx.arc(mouse.x, mouse.y, Math.min(viji.width, viji.height) / 50, 0, Math.PI * 2);
416
- ctx.stroke();
417
- ctx.setLineDash([]);
418
- }
419
-
420
- // Draw audio visualization
421
- if (showAudioViz.value && audio.isConnected) {
422
- ctx.fillStyle = 'rgba(0, 255, 255, 0.8)';
423
- ctx.font = '12px monospace';
424
- ctx.textAlign = 'left';
425
-
426
- let audioY = 10;
427
-
428
- // Audio connection status
429
- ctx.fillText('AUDIO ANALYSIS:', 10, audioY);
430
- audioY += 15;
431
- ctx.fillText('Connected: ' + (audio.isConnected ? 'Yes' : 'No'), 10, audioY);
432
- audioY += 15;
433
-
434
- // Volume meters
435
- ctx.fillText('Volume RMS: ' + (audio.volume.rms * 100).toFixed(1) + '%', 10, audioY);
436
- audioY += 15;
437
- ctx.fillText('Volume Peak: ' + (audio.volume.peak * 100).toFixed(1) + '%', 10, audioY);
438
- audioY += 15;
439
-
440
- // Frequency bands
441
- ctx.fillText('Bass: ' + (audio.bands.bass * 100).toFixed(1) + '%', 10, audioY);
442
- audioY += 15;
443
- ctx.fillText('Mid: ' + (audio.bands.mid * 100).toFixed(1) + '%', 10, audioY);
444
- audioY += 15;
445
- ctx.fillText('Treble: ' + (audio.bands.treble * 100).toFixed(1) + '%', 10, audioY);
446
- audioY += 15;
447
-
448
- // Visual frequency bars
449
- const barWidth = 4;
450
- const barSpacing = 6;
451
- const barHeight = 60;
452
- const barStartX = 10;
453
- const barStartY = audioY + 10;
454
-
455
- const bands = ['subBass', 'bass', 'lowMid', 'mid', 'highMid', 'presence', 'brilliance', 'treble'];
456
-
457
- for (let i = 0; i < bands.length; i++) {
458
- const bandValue = audio.bands[bands[i]] || 0;
459
- const x = barStartX + i * barSpacing;
460
- const height = bandValue * barHeight;
461
-
462
- // Background bar
463
- ctx.fillStyle = 'rgba(100, 100, 100, 0.3)';
464
- ctx.fillRect(x, barStartY, barWidth, barHeight);
465
-
466
- // Value bar with color gradient
467
- const hue = (i / bands.length) * 360;
468
- ctx.fillStyle = `hsla(${hue}, 80%, 60%, 0.8)`;
469
- ctx.fillRect(x, barStartY + barHeight - height, barWidth, height);
470
- }
471
-
472
- // Band labels
473
- ctx.fillStyle = 'rgba(0, 255, 255, 0.6)';
474
- ctx.font = '8px monospace';
475
- ctx.textAlign = 'center';
476
- for (let i = 0; i < bands.length; i++) {
477
- const x = barStartX + i * barSpacing + barWidth / 2;
478
- ctx.fillText(bands[i].substring(0, 3), x, barStartY + barHeight + 12);
479
- }
480
- }
481
-
482
- // Draw video overlay and info
483
- if (showVideoOverlay.value && video.isConnected) {
484
- // ? ACTUAL VIDEO RENDERING: Draw real video frame
485
- if (video.currentFrame) {
486
- ctx.save();
487
- ctx.globalAlpha = 0.7; // Semi-transparent overlay
488
-
489
- // Calculate video display size (maintain aspect ratio)
490
- const videoWidth = video.frameWidth;
491
- const videoHeight = video.frameHeight;
492
- const displayWidth = 200;
493
- const displayHeight = (videoHeight / videoWidth) * displayWidth;
494
-
495
- // Draw the actual video frame from OffscreenCanvas
496
- try {
497
- ctx.drawImage(
498
- video.currentFrame, // OffscreenCanvas from worker
499
- 0, 0, videoWidth, videoHeight, // Source rectangle
500
- viji.width - displayWidth - 10, 10, displayWidth, displayHeight // Destination rectangle
501
- );
502
- } catch (error) {
503
- // Fallback if direct drawing fails
504
- console.warn('Could not draw video frame:', error);
505
- ctx.fillStyle = 'rgba(255, 100, 100, 0.3)';
506
- ctx.fillRect(viji.width - displayWidth - 10, 10, displayWidth, displayHeight);
507
- ctx.fillStyle = 'white';
508
- ctx.font = '12px monospace';
509
- ctx.textAlign = 'center';
510
- ctx.fillText('Video Active', viji.width - displayWidth/2 - 10, 30);
511
- }
512
-
513
- ctx.restore();
514
-
515
- // Video info text
516
- ctx.fillStyle = 'rgba(0, 255, 255, 0.8)';
517
- ctx.font = '12px monospace';
518
- ctx.textAlign = 'right';
519
-
520
- let videoInfoY = 140;
521
- ctx.fillText('VIDEO ANALYSIS:', viji.width - 10, videoInfoY);
522
- videoInfoY += 15;
523
- ctx.fillText('Connected: ' + (video.isConnected ? 'Yes' : 'No'), viji.width - 10, videoInfoY);
524
- videoInfoY += 15;
525
- ctx.fillText('Resolution: ' + video.frameWidth + 'x' + video.frameHeight, viji.width - 10, videoInfoY);
526
- videoInfoY += 15;
527
- ctx.fillText('Frame Rate: ' + video.frameRate.toFixed(1) + ' FPS', viji.width - 10, videoInfoY);
528
- videoInfoY += 15;
529
- ctx.fillText('Motion: ' + (videoReactive.value ? 'Reactive' : 'Static'), viji.width - 10, videoInfoY);
530
- }
531
- }
532
- }