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.
Files changed (54) hide show
  1. package/dist/littlejs.d.ts +154 -158
  2. package/dist/littlejs.esm.js +573 -570
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +553 -553
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +392 -396
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/box2d/gameObjects.js +5 -5
  9. package/examples/breakout/game.js +3 -3
  10. package/examples/electron/game.js +1 -2
  11. package/examples/electron/index.html +2 -2
  12. package/examples/htmlMenu/game.js +0 -1
  13. package/examples/index.html +1 -1
  14. package/examples/logo.png +0 -0
  15. package/examples/logo2.png +0 -0
  16. package/examples/module/game.js +1 -2
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +4 -4
  19. package/examples/platformer/gameEffects.js +1 -1
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/shorts/base.html +4 -3
  22. package/examples/shorts/box2dPool.js +2 -2
  23. package/examples/shorts/empty.js +1 -1
  24. package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
  25. package/examples/shorts/fps.js +1 -1
  26. package/examples/shorts/helloWorld.js +2 -2
  27. package/examples/shorts/nineSlice.js +2 -2
  28. package/examples/shorts/slidingPuzzle.js +1 -1
  29. package/examples/starter/game.js +1 -2
  30. package/examples/starter/index.html +3 -2
  31. package/examples/stress/index.html +1 -1
  32. package/examples/typescript/game.js +1 -2
  33. package/examples/typescript/game.ts +1 -2
  34. package/package.json +1 -1
  35. package/plugins/box2d.js +8 -8
  36. package/plugins/drawUtilities.js +6 -6
  37. package/plugins/postProcess.js +13 -20
  38. package/plugins/uiSystem.js +16 -4
  39. package/reference.md +9 -11
  40. package/src/engine.js +40 -54
  41. package/src/engineAudio.js +5 -7
  42. package/src/engineBuild.js +1 -0
  43. package/src/engineDebug.js +163 -161
  44. package/src/engineDraw.js +108 -130
  45. package/src/engineExport.js +20 -17
  46. package/src/engineInput.js +4 -7
  47. package/src/engineMath.js +1201 -0
  48. package/src/engineMedals.js +7 -7
  49. package/src/engineObject.js +5 -4
  50. package/src/engineRelease.js +2 -4
  51. package/src/engineSettings.js +22 -32
  52. package/src/engineTileLayer.js +9 -10
  53. package/src/engineUtilities.js +34 -1187
  54. package/src/engineWebGL.js +13 -15
@@ -26,7 +26,7 @@ const debugPointSize = .5;
26
26
  * @type {boolean}
27
27
  * @default
28
28
  * @memberof Debug */
29
- let showWatermark = true;
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, downloadLink, debugCanvas;
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
- showWatermark = !showWatermark;
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
- const w = mainCanvas.width, h = mainCanvas.height;
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
- overlayContext.lineWidth = 2;
398
+ debugContext.lineWidth = 2;
442
399
  debugPrimitives.forEach(p=>
443
400
  {
444
- overlayContext.save();
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
- overlayContext.translate(pos.x|0, pos.y|0);
456
- overlayContext.rotate(angle);
457
- overlayContext.scale(1, p.text ? 1 : -1);
458
- overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
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
- overlayContext.font = p.size*scale + 'px '+ p.font;
462
- overlayContext.textAlign = 'center';
463
- overlayContext.textBaseline = 'middle';
464
- overlayContext.fillText(p.text, 0, 0);
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
- overlayContext.beginPath();
427
+ debugContext.beginPath();
470
428
  for (const point of p.points)
471
429
  {
472
430
  const p2 = point.scale(scale).floor();
473
- overlayContext.lineTo(p2.x, p2.y);
431
+ debugContext.lineTo(p2.x, p2.y);
474
432
  }
475
- overlayContext.closePath();
476
- p.fill && overlayContext.fill();
477
- overlayContext.stroke();
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
- overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
484
- overlayContext.fillRect(-1, -pointSize/2, 3, pointSize);
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 && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
492
- overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
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
- overlayContext.beginPath();
498
- overlayContext.arc(0, 0, p.size*scale/2, 0, 9);
499
- p.fill && overlayContext.fill();
500
- overlayContext.stroke();
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
- overlayContext.restore();
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
- const debugText = 'mouse pos = ' + mousePos +
519
- '\nmouse collision = ' + tileCollisionGetData(mousePos) +
520
- '\n\n--- object info ---\n' +
521
- debugObject.toString();
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
- overlayContext.save();
531
- overlayContext.fillStyle = '#fff';
532
- overlayContext.textAlign = 'left';
533
- overlayContext.textBaseline = 'top';
534
- overlayContext.font = fontSize + 'px monospace';
535
- overlayContext.shadowColor = '#000';
536
- overlayContext.shadowBlur = 9;
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
- overlayContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
542
- overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
543
- overlayContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
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
- overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
546
- overlayContext.fillText('Draw Count: ' + drawCount, x, y += h);
547
- overlayContext.fillText('---------', x, y += h);
548
- overlayContext.fillStyle = '#f00';
549
- overlayContext.fillText('ESC: Debug Overlay', x, y += h);
550
- overlayContext.fillStyle = debugPhysics ? '#f00' : '#fff';
551
- overlayContext.fillText('1: Debug Physics', x, y += h);
552
- overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
553
- overlayContext.fillText('2: Debug Particles', x, y += h);
554
- overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
555
- overlayContext.fillText('3: Debug Gamepads', x, y += h);
556
- overlayContext.fillStyle = debugRaycast ? '#f00' : '#fff';
557
- overlayContext.fillText('4: Debug Raycasts', x, y += h);
558
- overlayContext.fillStyle = '#fff';
559
- overlayContext.fillText('5: Save Screenshot', x, y += h);
560
- overlayContext.fillText('6: Toggle Video Capture', x, y += h);
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 && overlayContext.fillText('Mouse: ' + mousePressed, x, y += h);
574
- keysPressed && overlayContext.fillText('Keys: ' + keysPressed, x, y += h);
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 && overlayContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
542
+ buttonsPressed && debugContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
587
543
  }
588
544
  }
589
545
  else
590
546
  {
591
- overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
592
- overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
593
- overlayContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
594
- overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
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
- overlayContext.restore();
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, debugVideoCaptureTrack, debugVideoCaptureIcon, debugVideoCaptureTimer;
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
- if (debugVideoCaptureIsActive())
616
- return; // already recording
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 passing in 0 to only capture when requestFrame() is called
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
- debugVideoCaptureTrack = stream.getVideoTracks()[0];
622
- if (debugVideoCaptureTrack.applyConstraints)
623
- debugVideoCaptureTrack.applyConstraints({frameRate:60}); // force 60 fps
624
- debugVideoCapture = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp8'});
625
- debugVideoCapture.ondataavailable = (e)=> chunks.push(e.data);
626
- debugVideoCapture.onstop = ()=>
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
- downloadLink.download = 'capture.webm';
631
- downloadLink.href = url;
632
- downloadLink.click();
633
- URL.revokeObjectURL(url);
625
+ saveDataURL(url, 'capture.webm', 1e3);
634
626
  };
635
627
 
636
- if (audioMasterGain)
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
- const audioStreamDestination = audioContext.createMediaStreamDestination();
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
- if (!debugVideoCaptureIcon)
655
+ // save debug video info
656
+ debugVideoCapture =
651
657
  {
652
- // create recording icon to show it is capturing video
653
- debugVideoCaptureIcon = document.createElement('div');
654
- debugVideoCaptureIcon.style.position = 'absolute';
655
- debugVideoCaptureIcon.style.padding = '9px';
656
- debugVideoCaptureIcon.style.color = '#f00';
657
- debugVideoCaptureIcon.style.font = '50px monospace';
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
- if (!debugVideoCaptureIsActive())
670
- return; // not recording
670
+ ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
671
671
 
672
672
  // stop recording
673
- LOG(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
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
- if (!debugVideoCaptureIsActive())
683
- return; // not recording
684
+ ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
684
685
 
685
686
  // save the video frame
686
687
  combineCanvases();
687
- debugVideoCaptureTrack.requestFrame();
688
- debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
688
+ debugVideoCapture.videoTrack.requestFrame();
689
+ debugVideoCaptureIcon.textContent = '● REC '
690
+ + formatTime(debugVideoCapture.captureTimer);
689
691
  }
690
692
 
691
693
  ///////////////////////////////////////////////////////////////////////////////