littlejsengine 1.15.9 → 1.16.2
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/dist/littlejs.d.ts +154 -158
- package/dist/littlejs.esm.js +573 -570
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +553 -553
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +392 -396
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +5 -5
- package/examples/breakout/game.js +3 -3
- package/examples/electron/game.js +1 -2
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +0 -1
- package/examples/index.html +1 -1
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/game.js +1 -2
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +4 -4
- package/examples/platformer/gameEffects.js +1 -1
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -3
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/empty.js +1 -1
- package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/helloWorld.js +2 -2
- package/examples/shorts/nineSlice.js +2 -2
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/starter/game.js +1 -2
- package/examples/starter/index.html +3 -2
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +1 -2
- package/examples/typescript/game.ts +1 -2
- package/package.json +1 -1
- package/plugins/box2d.js +8 -8
- package/plugins/drawUtilities.js +6 -6
- package/plugins/postProcess.js +13 -20
- package/plugins/uiSystem.js +16 -4
- package/reference.md +9 -11
- package/src/engine.js +40 -54
- package/src/engineAudio.js +5 -7
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +163 -161
- package/src/engineDraw.js +108 -130
- package/src/engineExport.js +20 -17
- package/src/engineInput.js +4 -7
- package/src/engineMath.js +1201 -0
- package/src/engineMedals.js +7 -7
- package/src/engineObject.js +5 -4
- package/src/engineRelease.js +2 -4
- package/src/engineSettings.js +22 -32
- package/src/engineTileLayer.js +9 -10
- package/src/engineUtilities.js +34 -1187
- package/src/engineWebGL.js +13 -15
package/src/engineDebug.js
CHANGED
|
@@ -26,7 +26,7 @@ const debugPointSize = .5;
|
|
|
26
26
|
* @type {boolean}
|
|
27
27
|
* @default
|
|
28
28
|
* @memberof Debug */
|
|
29
|
-
let
|
|
29
|
+
let debugWatermark = true;
|
|
30
30
|
|
|
31
31
|
/** Key code used to toggle debug mode, Esc by default
|
|
32
32
|
* @type {string}
|
|
@@ -41,7 +41,7 @@ let debugKey = 'Escape';
|
|
|
41
41
|
let debugOverlay = false;
|
|
42
42
|
|
|
43
43
|
// Engine internal variables not exposed to documentation
|
|
44
|
-
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot
|
|
44
|
+
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot;
|
|
45
45
|
|
|
46
46
|
///////////////////////////////////////////////////////////////////////////////
|
|
47
47
|
// Debug helper functions
|
|
@@ -228,46 +228,6 @@ function debugClear() { debugPrimitives = []; }
|
|
|
228
228
|
* @memberof Debug */
|
|
229
229
|
function debugScreenshot() { debugTakeScreenshot = 1; }
|
|
230
230
|
|
|
231
|
-
/** Save a canvas to disk
|
|
232
|
-
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
233
|
-
* @param {string} [filename]
|
|
234
|
-
* @param {string} [type]
|
|
235
|
-
* @memberof Debug */
|
|
236
|
-
function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
|
|
237
|
-
{
|
|
238
|
-
if (canvas instanceof OffscreenCanvas)
|
|
239
|
-
{
|
|
240
|
-
// copy to temporary canvas and save
|
|
241
|
-
if (!debugCanvas)
|
|
242
|
-
debugCanvas = document.createElement('canvas');
|
|
243
|
-
debugCanvas.width = canvas.width;
|
|
244
|
-
debugCanvas.height = canvas.height;
|
|
245
|
-
debugCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
246
|
-
debugSaveDataURL(debugCanvas.toDataURL(type), filename);
|
|
247
|
-
}
|
|
248
|
-
else
|
|
249
|
-
debugSaveDataURL(canvas.toDataURL(type), filename);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** Save a text file to disk
|
|
253
|
-
* @param {string} text
|
|
254
|
-
* @param {string} [filename]
|
|
255
|
-
* @param {string} [type]
|
|
256
|
-
* @memberof Debug */
|
|
257
|
-
function debugSaveText(text, filename='text', type='text/plain')
|
|
258
|
-
{ debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
259
|
-
|
|
260
|
-
/** Save a data url to disk
|
|
261
|
-
* @param {string} dataURL
|
|
262
|
-
* @param {string} filename
|
|
263
|
-
* @memberof Debug */
|
|
264
|
-
function debugSaveDataURL(dataURL, filename)
|
|
265
|
-
{
|
|
266
|
-
downloadLink.download = filename;
|
|
267
|
-
downloadLink.href = dataURL;
|
|
268
|
-
downloadLink.click();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
231
|
/** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
|
|
272
232
|
* This is a good function to call at the start of your game to catch all errors
|
|
273
233
|
* In release builds this function has no effect
|
|
@@ -303,21 +263,18 @@ function debugShowErrors()
|
|
|
303
263
|
|
|
304
264
|
function debugInit()
|
|
305
265
|
{
|
|
306
|
-
// create link for saving screenshots
|
|
307
|
-
downloadLink = document.createElement('a');
|
|
308
266
|
}
|
|
309
267
|
|
|
310
268
|
function debugUpdate()
|
|
311
269
|
{
|
|
312
|
-
if (!debug)
|
|
313
|
-
return;
|
|
270
|
+
if (!debug) return;
|
|
314
271
|
|
|
315
272
|
if (keyWasPressed(debugKey)) // Esc
|
|
316
273
|
debugOverlay = !debugOverlay;
|
|
317
274
|
if (debugOverlay)
|
|
318
275
|
{
|
|
319
276
|
if (keyWasPressed('Digit0'))
|
|
320
|
-
|
|
277
|
+
debugWatermark = !debugWatermark;
|
|
321
278
|
if (keyWasPressed('Digit1'))
|
|
322
279
|
debugPhysics = !debugPhysics, debugParticles = false;
|
|
323
280
|
if (keyWasPressed('Digit2'))
|
|
@@ -328,9 +285,15 @@ function debugUpdate()
|
|
|
328
285
|
debugRaycast = !debugRaycast;
|
|
329
286
|
if (keyWasPressed('Digit5'))
|
|
330
287
|
debugScreenshot();
|
|
331
|
-
if (keyWasPressed('Digit6'))
|
|
332
|
-
debugVideoCaptureIsActive() ? debugVideoCaptureStop() : debugVideoCaptureStart();
|
|
333
288
|
}
|
|
289
|
+
if (debugVideoCaptureIsActive())
|
|
290
|
+
{
|
|
291
|
+
// control to stop video capture
|
|
292
|
+
if (!debugOverlay || keyWasPressed('Digit6'))
|
|
293
|
+
debugVideoCaptureStop();
|
|
294
|
+
}
|
|
295
|
+
else if (debugOverlay && keyWasPressed('Digit6'))
|
|
296
|
+
debugVideoCaptureStart();
|
|
334
297
|
}
|
|
335
298
|
|
|
336
299
|
function debugRender()
|
|
@@ -345,13 +308,11 @@ function debugRender()
|
|
|
345
308
|
{
|
|
346
309
|
// combine canvases, remove alpha and save
|
|
347
310
|
combineCanvases();
|
|
348
|
-
|
|
349
|
-
overlayContext.fillRect(0,0,w,h);
|
|
350
|
-
overlayContext.drawImage(mainCanvas, 0, 0);
|
|
351
|
-
debugSaveCanvas(overlayCanvas);
|
|
311
|
+
saveCanvas(mainCanvas);
|
|
352
312
|
debugTakeScreenshot = 0;
|
|
353
313
|
}
|
|
354
314
|
|
|
315
|
+
const debugContext = mainContext;
|
|
355
316
|
if (debugGamepads && gamepadsEnable)
|
|
356
317
|
{
|
|
357
318
|
// draw gamepads
|
|
@@ -399,9 +360,6 @@ function debugRender()
|
|
|
399
360
|
let debugObject;
|
|
400
361
|
if (debugOverlay)
|
|
401
362
|
{
|
|
402
|
-
const saveContext = mainContext;
|
|
403
|
-
mainContext = overlayContext;
|
|
404
|
-
|
|
405
363
|
// draw red rectangle around screen
|
|
406
364
|
const cameraSize = getCameraSize();
|
|
407
365
|
debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
|
|
@@ -433,15 +391,14 @@ function debugRender()
|
|
|
433
391
|
// show floored tile pick for tile collision
|
|
434
392
|
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
|
|
435
393
|
}
|
|
436
|
-
mainContext = saveContext;
|
|
437
394
|
}
|
|
438
395
|
|
|
439
396
|
{
|
|
440
397
|
// draw debug primitives
|
|
441
|
-
|
|
398
|
+
debugContext.lineWidth = 2;
|
|
442
399
|
debugPrimitives.forEach(p=>
|
|
443
400
|
{
|
|
444
|
-
|
|
401
|
+
debugContext.save();
|
|
445
402
|
|
|
446
403
|
// create canvas transform from world space to screen space
|
|
447
404
|
// without scaling because we want consistent pixel sizes
|
|
@@ -452,55 +409,56 @@ function debugRender()
|
|
|
452
409
|
scale = cameraScale;
|
|
453
410
|
angle -= cameraAngle;
|
|
454
411
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
412
|
+
debugContext.translate(pos.x|0, pos.y|0);
|
|
413
|
+
debugContext.rotate(angle);
|
|
414
|
+
debugContext.scale(1, p.text ? 1 : -1);
|
|
415
|
+
debugContext.fillStyle = p.color;
|
|
416
|
+
debugContext.strokeStyle = p.color;
|
|
459
417
|
if (p.text !== undefined)
|
|
460
418
|
{
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
419
|
+
debugContext.font = p.size*scale + 'px '+ p.font;
|
|
420
|
+
debugContext.textAlign = 'center';
|
|
421
|
+
debugContext.textBaseline = 'middle';
|
|
422
|
+
debugContext.fillText(p.text, 0, 0);
|
|
465
423
|
}
|
|
466
424
|
else if (p.points !== undefined)
|
|
467
425
|
{
|
|
468
426
|
// poly
|
|
469
|
-
|
|
427
|
+
debugContext.beginPath();
|
|
470
428
|
for (const point of p.points)
|
|
471
429
|
{
|
|
472
430
|
const p2 = point.scale(scale).floor();
|
|
473
|
-
|
|
431
|
+
debugContext.lineTo(p2.x, p2.y);
|
|
474
432
|
}
|
|
475
|
-
|
|
476
|
-
p.fill &&
|
|
477
|
-
|
|
433
|
+
debugContext.closePath();
|
|
434
|
+
p.fill && debugContext.fill();
|
|
435
|
+
debugContext.stroke();
|
|
478
436
|
}
|
|
479
437
|
else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
|
|
480
438
|
{
|
|
481
439
|
// point
|
|
482
440
|
const pointSize = debugPointSize * scale;
|
|
483
|
-
|
|
484
|
-
|
|
441
|
+
debugContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
442
|
+
debugContext.fillRect(-1, -pointSize/2, 3, pointSize);
|
|
485
443
|
}
|
|
486
444
|
else if (p.size.x !== undefined)
|
|
487
445
|
{
|
|
488
446
|
// rect
|
|
489
447
|
const s = p.size.scale(scale).floor();
|
|
490
448
|
const w = s.x, h = s.y;
|
|
491
|
-
p.fill &&
|
|
492
|
-
|
|
449
|
+
p.fill && debugContext.fillRect(-w/2|0, -h/2|0, w, h);
|
|
450
|
+
debugContext.strokeRect(-w/2|0, -h/2|0, w, h);
|
|
493
451
|
}
|
|
494
452
|
else
|
|
495
453
|
{
|
|
496
454
|
// circle
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
p.fill &&
|
|
500
|
-
|
|
455
|
+
debugContext.beginPath();
|
|
456
|
+
debugContext.arc(0, 0, p.size*scale/2, 0, 9);
|
|
457
|
+
p.fill && debugContext.fill();
|
|
458
|
+
debugContext.stroke();
|
|
501
459
|
}
|
|
502
460
|
|
|
503
|
-
|
|
461
|
+
debugContext.restore();
|
|
504
462
|
});
|
|
505
463
|
|
|
506
464
|
// remove expired primitives
|
|
@@ -509,55 +467,53 @@ function debugRender()
|
|
|
509
467
|
|
|
510
468
|
if (debugObject)
|
|
511
469
|
{
|
|
512
|
-
const saveContext = mainContext;
|
|
513
|
-
mainContext = overlayContext;
|
|
514
470
|
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
515
471
|
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
516
472
|
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
|
|
517
473
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
'\
|
|
521
|
-
|
|
474
|
+
let debugText = 'mouse pos = ' + mousePos;
|
|
475
|
+
if (tileCollisionLayers.length)
|
|
476
|
+
debugText += '\nmouse collision = ' + tileCollisionGetData(mousePos);
|
|
477
|
+
debugText += '\n\n--- object info ---\n';
|
|
478
|
+
debugText += debugObject.toString();
|
|
522
479
|
drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
|
|
523
|
-
mainContext = saveContext;
|
|
524
480
|
}
|
|
525
481
|
|
|
526
482
|
{
|
|
527
483
|
// draw debug overlay
|
|
528
484
|
const fontSize = 20;
|
|
529
485
|
const lineHeight = fontSize * 1.2 | 0;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
486
|
+
debugContext.save();
|
|
487
|
+
debugContext.fillStyle = '#fff';
|
|
488
|
+
debugContext.textAlign = 'left';
|
|
489
|
+
debugContext.textBaseline = 'top';
|
|
490
|
+
debugContext.font = fontSize + 'px monospace';
|
|
491
|
+
debugContext.shadowColor = '#000';
|
|
492
|
+
debugContext.shadowBlur = 9;
|
|
537
493
|
|
|
538
494
|
let x = 9, y = 0, h = lineHeight;
|
|
539
495
|
if (debugOverlay)
|
|
540
496
|
{
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
497
|
+
debugContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
|
|
498
|
+
debugContext.fillText('Time: ' + formatTime(time), x, y += h);
|
|
499
|
+
debugContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
|
|
544
500
|
x, y += h);
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
501
|
+
debugContext.fillText('Objects: ' + engineObjects.length, x, y += h);
|
|
502
|
+
debugContext.fillText('Draw Count: ' + drawCount, x, y += h);
|
|
503
|
+
debugContext.fillText('---------', x, y += h);
|
|
504
|
+
debugContext.fillStyle = '#f00';
|
|
505
|
+
debugContext.fillText('ESC: Debug Overlay', x, y += h);
|
|
506
|
+
debugContext.fillStyle = debugPhysics ? '#f00' : '#fff';
|
|
507
|
+
debugContext.fillText('1: Debug Physics', x, y += h);
|
|
508
|
+
debugContext.fillStyle = debugParticles ? '#f00' : '#fff';
|
|
509
|
+
debugContext.fillText('2: Debug Particles', x, y += h);
|
|
510
|
+
debugContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
511
|
+
debugContext.fillText('3: Debug Gamepads', x, y += h);
|
|
512
|
+
debugContext.fillStyle = debugRaycast ? '#f00' : '#fff';
|
|
513
|
+
debugContext.fillText('4: Debug Raycasts', x, y += h);
|
|
514
|
+
debugContext.fillStyle = '#fff';
|
|
515
|
+
debugContext.fillText('5: Save Screenshot', x, y += h);
|
|
516
|
+
debugContext.fillText('6: Toggle Video Capture', x, y += h);
|
|
561
517
|
|
|
562
518
|
let keysPressed = '';
|
|
563
519
|
let mousePressed = '';
|
|
@@ -570,8 +526,8 @@ function debugRender()
|
|
|
570
526
|
else if (keyIsDown(i, 0))
|
|
571
527
|
keysPressed += i + ' ' ;
|
|
572
528
|
}
|
|
573
|
-
mousePressed &&
|
|
574
|
-
keysPressed &&
|
|
529
|
+
mousePressed && debugContext.fillText('Mouse: ' + mousePressed, x, y += h);
|
|
530
|
+
keysPressed && debugContext.fillText('Keys: ' + keysPressed, x, y += h);
|
|
575
531
|
|
|
576
532
|
// show gamepad buttons
|
|
577
533
|
for (let i = 1; i < inputData.length; i++)
|
|
@@ -583,26 +539,49 @@ function debugRender()
|
|
|
583
539
|
if (keyIsDown(j, i))
|
|
584
540
|
buttonsPressed += j + ' ' ;
|
|
585
541
|
}
|
|
586
|
-
buttonsPressed &&
|
|
542
|
+
buttonsPressed && debugContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
|
|
587
543
|
}
|
|
588
544
|
}
|
|
589
545
|
else
|
|
590
546
|
{
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
547
|
+
debugContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
548
|
+
debugContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
549
|
+
debugContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
550
|
+
debugContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
595
551
|
}
|
|
596
552
|
|
|
597
|
-
|
|
553
|
+
debugContext.restore();
|
|
598
554
|
}
|
|
599
555
|
}
|
|
600
556
|
|
|
557
|
+
function debugRenderPost()
|
|
558
|
+
{
|
|
559
|
+
if (debugVideoCaptureIsActive())
|
|
560
|
+
{
|
|
561
|
+
debugVideoCaptureUpdate();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!debugWatermark) return;
|
|
566
|
+
|
|
567
|
+
// update fps display
|
|
568
|
+
mainContext.textAlign = 'right';
|
|
569
|
+
mainContext.textBaseline = 'top';
|
|
570
|
+
mainContext.font = '1em monospace';
|
|
571
|
+
mainContext.fillStyle = '#000';
|
|
572
|
+
const text = engineName + ' v' + engineVersion + ' / '
|
|
573
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
574
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
575
|
+
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
576
|
+
mainContext.fillStyle = '#fff';
|
|
577
|
+
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
578
|
+
}
|
|
579
|
+
|
|
601
580
|
///////////////////////////////////////////////////////////////////////////////
|
|
602
581
|
// video capture - records video and audio at 60 fps using MediaRecorder API
|
|
603
582
|
|
|
604
583
|
// internal variables used to capture video
|
|
605
|
-
let debugVideoCapture,
|
|
584
|
+
let debugVideoCapture, debugVideoCaptureIcon;
|
|
606
585
|
|
|
607
586
|
/** Check if video capture is active
|
|
608
587
|
* @memberof Debug */
|
|
@@ -612,80 +591,103 @@ function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
|
|
|
612
591
|
* @memberof Debug */
|
|
613
592
|
function debugVideoCaptureStart()
|
|
614
593
|
{
|
|
615
|
-
|
|
616
|
-
|
|
594
|
+
ASSERT(!debugVideoCaptureIsActive(), 'Already capturing video!');
|
|
595
|
+
|
|
596
|
+
if (!debugVideoCaptureIcon)
|
|
597
|
+
{
|
|
598
|
+
// create recording icon to show it is capturing video
|
|
599
|
+
debugVideoCaptureIcon = document.createElement('div');
|
|
600
|
+
debugVideoCaptureIcon.style.position = 'absolute';
|
|
601
|
+
debugVideoCaptureIcon.style.padding = '9px';
|
|
602
|
+
debugVideoCaptureIcon.style.color = '#f00';
|
|
603
|
+
debugVideoCaptureIcon.style.font = '50px monospace';
|
|
604
|
+
document.body.appendChild(debugVideoCaptureIcon);
|
|
605
|
+
}
|
|
606
|
+
// show recording icon
|
|
607
|
+
debugVideoCaptureIcon.textContent = '';
|
|
608
|
+
debugVideoCaptureIcon.style.display = '';
|
|
617
609
|
|
|
618
|
-
// captureStream
|
|
610
|
+
// setup captureStream to capture manually by passing 0
|
|
619
611
|
const stream = mainCanvas.captureStream(0);
|
|
612
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
613
|
+
const captureTimer = new Timer(0, true);
|
|
620
614
|
const chunks = [];
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
615
|
+
videoTrack.applyConstraints({frameRate:frameRate});
|
|
616
|
+
|
|
617
|
+
// set up the media recorder
|
|
618
|
+
const mediaRecorder = new MediaRecorder(stream,
|
|
619
|
+
{mimeType:'video/webm;codecs=vp8'});
|
|
620
|
+
mediaRecorder.ondataavailable = (e)=> chunks.push(e.data);
|
|
621
|
+
mediaRecorder.onstop = ()=>
|
|
627
622
|
{
|
|
628
623
|
const blob = new Blob(chunks, {type: 'video/webm'});
|
|
629
624
|
const url = URL.createObjectURL(blob);
|
|
630
|
-
|
|
631
|
-
downloadLink.href = url;
|
|
632
|
-
downloadLink.click();
|
|
633
|
-
URL.revokeObjectURL(url);
|
|
625
|
+
saveDataURL(url, 'capture.webm', 1e3);
|
|
634
626
|
};
|
|
635
627
|
|
|
636
|
-
|
|
628
|
+
let audioStreamDestination, silentAudioSource;
|
|
629
|
+
if (soundEnable)
|
|
637
630
|
{
|
|
631
|
+
// create silent audio source
|
|
632
|
+
// fixes issue where video can not start recording without audio
|
|
633
|
+
silentAudioSource = new ConstantSourceNode(audioContext, { offset: 0 });
|
|
634
|
+
silentAudioSource.connect(audioMasterGain);
|
|
635
|
+
silentAudioSource.start();
|
|
636
|
+
|
|
638
637
|
// connect to audio master gain node
|
|
639
|
-
|
|
638
|
+
audioStreamDestination = audioContext.createMediaStreamDestination();
|
|
640
639
|
audioMasterGain.connect(audioStreamDestination);
|
|
641
640
|
for (const track of audioStreamDestination.stream.getAudioTracks())
|
|
642
641
|
stream.addTrack(track); // add audio tracks to capture stream
|
|
643
642
|
}
|
|
644
643
|
|
|
645
644
|
// start recording
|
|
645
|
+
try { mediaRecorder.start(); }
|
|
646
|
+
catch(e)
|
|
647
|
+
{
|
|
648
|
+
LOG('Video capture not supported in this browser!');
|
|
649
|
+
silentAudioSource?.stop();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
646
653
|
LOG('Video capture started.');
|
|
647
|
-
debugVideoCapture.start();
|
|
648
|
-
debugVideoCaptureTimer = new Timer(0);
|
|
649
654
|
|
|
650
|
-
|
|
655
|
+
// save debug video info
|
|
656
|
+
debugVideoCapture =
|
|
651
657
|
{
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
document.body.appendChild(debugVideoCaptureIcon);
|
|
659
|
-
}
|
|
660
|
-
// show recording icon
|
|
661
|
-
debugVideoCaptureIcon.textContent = '';
|
|
662
|
-
debugVideoCaptureIcon.style.display = '';
|
|
658
|
+
mediaRecorder,
|
|
659
|
+
captureTimer,
|
|
660
|
+
videoTrack,
|
|
661
|
+
silentAudioSource,
|
|
662
|
+
audioStreamDestination
|
|
663
|
+
};
|
|
663
664
|
}
|
|
664
665
|
|
|
665
666
|
/** Stop capturing video and save to disk
|
|
666
667
|
* @memberof Debug */
|
|
667
668
|
function debugVideoCaptureStop()
|
|
668
669
|
{
|
|
669
|
-
|
|
670
|
-
return; // not recording
|
|
670
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
671
671
|
|
|
672
672
|
// stop recording
|
|
673
|
-
LOG(`Video capture ended. ${
|
|
674
|
-
debugVideoCapture.stop();
|
|
675
|
-
debugVideoCapture = 0;
|
|
673
|
+
LOG(`Video capture ended. ${debugVideoCapture.captureTimer.get().toFixed(2)} seconds recorded.`);
|
|
676
674
|
debugVideoCaptureIcon.style.display = 'none';
|
|
675
|
+
debugVideoCapture.silentAudioSource?.stop();
|
|
676
|
+
debugVideoCapture.mediaRecorder?.stop();
|
|
677
|
+
debugVideoCapture.videoTrack?.stop();
|
|
678
|
+
debugVideoCapture = undefined;
|
|
677
679
|
}
|
|
678
680
|
|
|
679
681
|
// update video capture, called automatically by engine
|
|
680
682
|
function debugVideoCaptureUpdate()
|
|
681
683
|
{
|
|
682
|
-
|
|
683
|
-
return; // not recording
|
|
684
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
684
685
|
|
|
685
686
|
// save the video frame
|
|
686
687
|
combineCanvases();
|
|
687
|
-
|
|
688
|
-
debugVideoCaptureIcon.textContent = '● REC '
|
|
688
|
+
debugVideoCapture.videoTrack.requestFrame();
|
|
689
|
+
debugVideoCaptureIcon.textContent = '● REC '
|
|
690
|
+
+ formatTime(debugVideoCapture.captureTimer);
|
|
689
691
|
}
|
|
690
692
|
|
|
691
693
|
///////////////////////////////////////////////////////////////////////////////
|