etro 0.8.0 → 0.8.3

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 (97) hide show
  1. package/.github/workflows/nodejs.yml +3 -1
  2. package/.github/workflows/shipjs-trigger.yml +29 -0
  3. package/CHANGELOG.md +36 -13
  4. package/CODE_OF_CONDUCT.md +5 -5
  5. package/CONTRIBUTING.md +22 -72
  6. package/README.md +2 -2
  7. package/dist/effect/base.d.ts +14 -1
  8. package/dist/etro-cjs.js +189 -230
  9. package/dist/etro-iife.js +189 -230
  10. package/dist/layer/base.d.ts +13 -0
  11. package/eslint.conf.js +2 -1
  12. package/eslint.test-conf.js +1 -0
  13. package/examples/application/readme-screenshot.html +4 -8
  14. package/examples/application/video-player.html +3 -4
  15. package/examples/introduction/effects.html +23 -4
  16. package/karma.conf.js +4 -2
  17. package/package.json +8 -4
  18. package/scripts/gen-effect-samples.html +0 -3
  19. package/ship.config.js +80 -0
  20. package/src/effect/base.ts +29 -10
  21. package/src/effect/gaussian-blur.ts +10 -10
  22. package/src/effect/pixelate.ts +1 -2
  23. package/src/effect/shader.ts +18 -22
  24. package/src/effect/stack.ts +8 -4
  25. package/src/effect/transform.ts +13 -14
  26. package/src/event.ts +8 -14
  27. package/src/layer/audio-source.ts +16 -14
  28. package/src/layer/audio.ts +1 -2
  29. package/src/layer/base.ts +26 -7
  30. package/src/layer/visual.ts +11 -6
  31. package/src/movie.ts +70 -64
  32. package/src/util.ts +50 -57
  33. package/docs/effect.js.html +0 -1215
  34. package/docs/event.js.html +0 -145
  35. package/docs/index.html +0 -81
  36. package/docs/index.js.html +0 -92
  37. package/docs/layer.js.html +0 -888
  38. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  39. package/docs/module-effect.Brightness.html +0 -339
  40. package/docs/module-effect.Channels.html +0 -319
  41. package/docs/module-effect.ChromaKey.html +0 -611
  42. package/docs/module-effect.Contrast.html +0 -339
  43. package/docs/module-effect.EllipticalMask.html +0 -200
  44. package/docs/module-effect.GaussianBlur.html +0 -202
  45. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  46. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  47. package/docs/module-effect.Pixelate.html +0 -330
  48. package/docs/module-effect.Shader.html +0 -1227
  49. package/docs/module-effect.Stack.html +0 -406
  50. package/docs/module-effect.Transform.Matrix.html +0 -193
  51. package/docs/module-effect.Transform.html +0 -1174
  52. package/docs/module-effect.html +0 -148
  53. package/docs/module-event.html +0 -473
  54. package/docs/module-index.html +0 -186
  55. package/docs/module-layer-Media.html +0 -1116
  56. package/docs/module-layer-MediaMixin.html +0 -164
  57. package/docs/module-layer.Audio.html +0 -1188
  58. package/docs/module-layer.Base.html +0 -629
  59. package/docs/module-layer.Image.html +0 -1421
  60. package/docs/module-layer.Text.html +0 -1731
  61. package/docs/module-layer.Video.html +0 -1938
  62. package/docs/module-layer.Visual.html +0 -1698
  63. package/docs/module-layer.html +0 -137
  64. package/docs/module-movie.html +0 -3118
  65. package/docs/module-util.Color.html +0 -702
  66. package/docs/module-util.Font.html +0 -395
  67. package/docs/module-util.html +0 -845
  68. package/docs/movie.js.html +0 -689
  69. package/docs/scripts/collapse.js +0 -20
  70. package/docs/scripts/linenumber.js +0 -25
  71. package/docs/scripts/nav.js +0 -12
  72. package/docs/scripts/polyfill.js +0 -4
  73. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  74. package/docs/scripts/prettify/lang-css.js +0 -2
  75. package/docs/scripts/prettify/prettify.js +0 -28
  76. package/docs/scripts/search.js +0 -83
  77. package/docs/styles/jsdoc.css +0 -671
  78. package/docs/styles/prettify.css +0 -79
  79. package/docs/util.js.html +0 -503
  80. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  81. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  82. package/spec/assets/effect/grayscale.png +0 -0
  83. package/spec/assets/effect/original.png +0 -0
  84. package/spec/assets/effect/pixelate.png +0 -0
  85. package/spec/assets/effect/transform/multiply.png +0 -0
  86. package/spec/assets/effect/transform/rotate.png +0 -0
  87. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  88. package/spec/assets/effect/transform/scale.png +0 -0
  89. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  90. package/spec/assets/effect/transform/translate.png +0 -0
  91. package/spec/assets/layer/audio.wav +0 -0
  92. package/spec/assets/layer/image.jpg +0 -0
  93. package/spec/effect.spec.js +0 -421
  94. package/spec/event.spec.js +0 -39
  95. package/spec/layer.spec.js +0 -307
  96. package/spec/movie.spec.js +0 -346
  97. package/spec/util.spec.js +0 -294
package/dist/etro-cjs.js CHANGED
@@ -52,14 +52,11 @@ var TypeId = /** @class */ (function () {
52
52
  this._parts = id.split('.');
53
53
  }
54
54
  TypeId.prototype.contains = function (other) {
55
- if (other._parts.length > this._parts.length) {
55
+ if (other._parts.length > this._parts.length)
56
56
  return false;
57
- }
58
- for (var i = 0; i < other._parts.length; i++) {
59
- if (other._parts[i] !== this._parts[i]) {
57
+ for (var i = 0; i < other._parts.length; i++)
58
+ if (other._parts[i] !== this._parts[i])
60
59
  return false;
61
- }
62
- }
63
60
  return true;
64
61
  };
65
62
  TypeId.prototype.toString = function () {
@@ -76,9 +73,8 @@ var TypeId = /** @class */ (function () {
76
73
  * @param listener
77
74
  */
78
75
  function subscribe(target, type, listener) {
79
- if (!listeners.has(target)) {
76
+ if (!listeners.has(target))
80
77
  listeners.set(target, []);
81
- }
82
78
  listeners.get(target).push({ type: new TypeId(type), listener: listener });
83
79
  }
84
80
  /**
@@ -92,9 +88,8 @@ function subscribe(target, type, listener) {
92
88
  function unsubscribe(target, listener) {
93
89
  // Make sure `listener` has been added with `subscribe`.
94
90
  if (!listeners.has(target) ||
95
- !listeners.get(target).map(function (pair) { return pair.listener; }).includes(listener)) {
91
+ !listeners.get(target).map(function (pair) { return pair.listener; }).includes(listener))
96
92
  throw new Error('No matching event listener to remove');
97
- }
98
93
  var removed = listeners.get(target)
99
94
  .filter(function (pair) { return pair.listener !== listener; });
100
95
  listeners.set(target, removed);
@@ -111,17 +106,15 @@ function publish(target, type, event) {
111
106
  event.target = target; // could be a proxy
112
107
  event.type = type;
113
108
  var t = new TypeId(type);
114
- if (!listeners.has(target)) {
109
+ if (!listeners.has(target))
115
110
  // No event fired
116
111
  return null;
117
- }
118
112
  // Call event listeners for this event.
119
113
  var listenersForType = [];
120
114
  for (var i = 0; i < listeners.get(target).length; i++) {
121
115
  var item = listeners.get(target)[i];
122
- if (t.contains(item.type)) {
116
+ if (t.contains(item.type))
123
117
  listenersForType.push(item.listener);
124
- }
125
118
  }
126
119
  for (var i = 0; i < listenersForType.length; i++) {
127
120
  var listener = listenersForType[i];
@@ -149,9 +142,8 @@ var event = /*#__PURE__*/Object.freeze({
149
142
  function getPropertyDescriptor(obj, name) {
150
143
  do {
151
144
  var propDesc = Object.getOwnPropertyDescriptor(obj, name);
152
- if (propDesc) {
145
+ if (propDesc)
153
146
  return propDesc;
154
- }
155
147
  obj = Object.getPrototypeOf(obj);
156
148
  } while (obj);
157
149
  return undefined;
@@ -166,35 +158,30 @@ function getPropertyDescriptor(obj, name) {
166
158
  function applyOptions(options, destObj) {
167
159
  var defaultOptions = destObj.getDefaultOptions();
168
160
  // Validate; make sure `keys` doesn't have any extraneous items
169
- for (var option in options) {
161
+ for (var option in options)
170
162
  // eslint-disable-next-line no-prototype-builtins
171
- if (!defaultOptions.hasOwnProperty(option)) {
163
+ if (!defaultOptions.hasOwnProperty(option))
172
164
  throw new Error("Invalid option: '" + option + "'");
173
- }
174
- }
175
165
  // Merge options and defaultOptions
176
166
  options = __assign(__assign({}, defaultOptions), options);
177
167
  // Copy options
178
168
  for (var option in options) {
179
169
  var propDesc = getPropertyDescriptor(destObj, option);
180
170
  // Update the property as long as the property has not been set (unless if it has a setter)
181
- if (!propDesc || propDesc.set) {
171
+ if (!propDesc || propDesc.set)
182
172
  destObj[option] = options[option];
183
- }
184
173
  }
185
174
  }
186
175
  // This must be cleared at the start of each frame
187
176
  var valCache = new WeakMap();
188
177
  function cacheValue(element, path, value) {
189
178
  // Initiate movie cache
190
- if (!valCache.has(element.movie)) {
179
+ if (!valCache.has(element.movie))
191
180
  valCache.set(element.movie, new WeakMap());
192
- }
193
181
  var movieCache = valCache.get(element.movie);
194
182
  // Iniitate element cache
195
- if (!movieCache.has(element)) {
183
+ if (!movieCache.has(element))
196
184
  movieCache.set(element, {});
197
- }
198
185
  var elementCache = movieCache.get(element);
199
186
  // Cache the value
200
187
  elementCache[path] = value;
@@ -234,16 +221,13 @@ var KeyFrame = /** @class */ (function () {
234
221
  return this;
235
222
  };
236
223
  KeyFrame.prototype.evaluate = function (time) {
237
- if (this.value.length === 0) {
224
+ if (this.value.length === 0)
238
225
  throw new Error('Empty keyframe');
239
- }
240
- if (time === undefined) {
226
+ if (time === undefined)
241
227
  throw new Error('|time| is undefined or null');
242
- }
243
228
  var firstTime = this.value[0][0];
244
- if (time < firstTime) {
229
+ if (time < firstTime)
245
230
  throw new Error('No keyframe point before |time|');
246
- }
247
231
  // I think reduce are slow to do per-frame (or more)?
248
232
  for (var i = 0; i < this.value.length; i++) {
249
233
  var startTime = this.value[i][0];
@@ -252,7 +236,7 @@ var KeyFrame = /** @class */ (function () {
252
236
  if (i + 1 < this.value.length) {
253
237
  var endTime = this.value[i + 1][0];
254
238
  var endValue = this.value[i + 1][1];
255
- if (startTime <= time && time < endTime) {
239
+ if (startTime <= time && time < endTime)
256
240
  // No need for endValue if it is flat interpolation
257
241
  // TODO: support custom interpolation for 'other' types?
258
242
  if (!(typeof startValue === 'number' || typeof endValue === 'object')) {
@@ -268,7 +252,6 @@ var KeyFrame = /** @class */ (function () {
268
252
  endValue, // eslint-disable-line @typescript-eslint/ban-types
269
253
  percentProgress, this.interpolationKeys);
270
254
  }
271
- }
272
255
  }
273
256
  else {
274
257
  // Repeat last value forever
@@ -294,28 +277,23 @@ var KeyFrame = /** @class */ (function () {
294
277
  // TODO: Is this function efficient?
295
278
  // TODO: Update doc @params to allow for keyframes
296
279
  function val(element, path, time) {
297
- if (hasCachedValue(element, path)) {
280
+ if (hasCachedValue(element, path))
298
281
  return getCachedValue(element, path);
299
- }
300
282
  // Get property of element at path
301
283
  var pathParts = path.split('.');
302
284
  var property = element[pathParts.shift()];
303
- while (pathParts.length > 0) {
285
+ while (pathParts.length > 0)
304
286
  property = property[pathParts.shift()];
305
- }
306
287
  // Property filter function
307
288
  var process = element.propertyFilters[path];
308
289
  var value;
309
- if (property instanceof KeyFrame) {
290
+ if (property instanceof KeyFrame)
310
291
  value = property.evaluate(time);
311
- }
312
- else if (typeof property === 'function') {
292
+ else if (typeof property === 'function')
313
293
  value = property(element, time); // TODO? add more args
314
- }
315
- else {
294
+ else
316
295
  // Simple value
317
296
  value = property;
318
- }
319
297
  return cacheValue(element, path, process ? process.call(element, value) : value);
320
298
  }
321
299
  /* export function floorInterp(x1, x2, t, objectKeys) {
@@ -326,18 +304,15 @@ function val(element, path, time) {
326
304
  }, Object.create(Object.getPrototypeOf(x1)));
327
305
  } */
328
306
  function linearInterp(x1, x2, t, objectKeys) {
329
- if (typeof x1 !== typeof x2) {
307
+ if (typeof x1 !== typeof x2)
330
308
  throw new Error('Type mismatch');
331
- }
332
- if (typeof x1 !== 'number' && typeof x1 !== 'object') {
309
+ if (typeof x1 !== 'number' && typeof x1 !== 'object')
333
310
  // Flat interpolation (floor)
334
311
  return x1;
335
- }
336
312
  if (typeof x1 === 'object') { // to work with objects (including arrays)
337
313
  // TODO: make this code DRY
338
- if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
314
+ if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
339
315
  throw new Error('Prototype mismatch');
340
- }
341
316
  // Preserve prototype of objects
342
317
  var int = Object.create(Object.getPrototypeOf(x1));
343
318
  // Take the intersection of properties
@@ -345,9 +320,8 @@ function linearInterp(x1, x2, t, objectKeys) {
345
320
  for (var i = 0; i < keys.length; i++) {
346
321
  var key = keys[i];
347
322
  // eslint-disable-next-line no-prototype-builtins
348
- if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
323
+ if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
349
324
  continue;
350
- }
351
325
  int[key] = linearInterp(x1[key], x2[key], t);
352
326
  }
353
327
  return int;
@@ -355,17 +329,14 @@ function linearInterp(x1, x2, t, objectKeys) {
355
329
  return (1 - t) * x1 + t * x2;
356
330
  }
357
331
  function cosineInterp(x1, x2, t, objectKeys) {
358
- if (typeof x1 !== typeof x2) {
332
+ if (typeof x1 !== typeof x2)
359
333
  throw new Error('Type mismatch');
360
- }
361
- if (typeof x1 !== 'number' && typeof x1 !== 'object') {
334
+ if (typeof x1 !== 'number' && typeof x1 !== 'object')
362
335
  // Flat interpolation (floor)
363
336
  return x1;
364
- }
365
337
  if (typeof x1 === 'object' && typeof x2 === 'object') { // to work with objects (including arrays)
366
- if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
338
+ if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
367
339
  throw new Error('Prototype mismatch');
368
- }
369
340
  // Preserve prototype of objects
370
341
  var int = Object.create(Object.getPrototypeOf(x1));
371
342
  // Take the intersection of properties
@@ -373,9 +344,8 @@ function cosineInterp(x1, x2, t, objectKeys) {
373
344
  for (var i = 0; i < keys.length; i++) {
374
345
  var key = keys[i];
375
346
  // eslint-disable-next-line no-prototype-builtins
376
- if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
347
+ if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
377
348
  continue;
378
- }
379
349
  int[key] = cosineInterp(x1[key], x2[key], t);
380
350
  }
381
351
  return int;
@@ -505,12 +475,10 @@ function mapPixels(mapper, canvas, ctx, x, y, width, height, flush) {
505
475
  width = width || canvas.width;
506
476
  height = height || canvas.height;
507
477
  var frame = ctx.getImageData(x, y, width, height);
508
- for (var i = 0, l = frame.data.length; i < l; i += 4) {
478
+ for (var i = 0, l = frame.data.length; i < l; i += 4)
509
479
  mapper(frame.data, i);
510
- }
511
- if (flush) {
480
+ if (flush)
512
481
  ctx.putImageData(frame, x, y);
513
- }
514
482
  }
515
483
  /**
516
484
  * <p>Emits "change" event when public properties updated, recursively.
@@ -527,17 +495,17 @@ function watchPublic(target) {
527
495
  // Public API property updated, emit 'modify' event.
528
496
  publish(proxy, target.type + ".change.modify", { property: getPath(receiver, prop), newValue: val });
529
497
  };
530
- var check = function (prop) { return !(prop.startsWith('_') || target.publicExcludes.includes(prop)); };
498
+ var canWatch = function (receiver, prop) { return !prop.startsWith('_') &&
499
+ (receiver.publicExcludes === undefined || !receiver.publicExcludes.includes(prop)); };
531
500
  // The path to each child property (each is a unique proxy)
532
501
  var paths = new WeakMap();
533
502
  var handler = {
534
503
  set: function (obj, prop, val, receiver) {
535
504
  // Recurse
536
- if (typeof val === 'object' && val !== null && !paths.has(val) && check(prop)) {
505
+ if (typeof val === 'object' && val !== null && !paths.has(val) && canWatch(receiver, prop)) {
537
506
  val = new Proxy(val, handler);
538
507
  paths.set(val, getPath(receiver, prop));
539
508
  }
540
- var was = prop in obj;
541
509
  // Set property or attribute
542
510
  // Search prototype chain for the closest setter
543
511
  var objProto = obj;
@@ -549,15 +517,12 @@ function watchPublic(target) {
549
517
  break;
550
518
  }
551
519
  }
552
- if (!objProto) {
520
+ if (!objProto)
553
521
  // Couldn't find setter; set value on instance
554
522
  obj[prop] = val;
555
- }
556
- // Check if it already existed and if it's a valid property to watch, if
557
- // on root object.
558
- if (obj !== target || (was && check(prop))) {
523
+ // Check if the property isn't blacklisted in publicExcludes.
524
+ if (canWatch(receiver, prop))
559
525
  callback(prop, val, receiver);
560
- }
561
526
  return true;
562
527
  }
563
528
  };
@@ -597,23 +562,20 @@ function AudioSourceMixin(superclass) {
597
562
  applyOptions(options, _this);
598
563
  var load = function () {
599
564
  // TODO: && ?
600
- if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0) {
565
+ if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0)
601
566
  throw new Error('Invalid options.duration or options.sourceStartTime');
602
- }
603
567
  _this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
604
568
  _this.duration = _this._unstretchedDuration / (_this.playbackRate);
605
569
  // onload will use `this`, and can't bind itself because it's before
606
570
  // super()
607
571
  onload && onload.bind(_this)(_this.source, options);
608
572
  };
609
- if (_this.source.readyState >= 2) {
573
+ if (_this.source.readyState >= 2)
610
574
  // this frame's data is available now
611
575
  load();
612
- }
613
- else {
576
+ else
614
577
  // when this frame's data is available
615
578
  _this.source.addEventListener('loadedmetadata', load);
616
- }
617
579
  _this.source.addEventListener('durationchange', function () {
618
580
  _this.duration = options.duration || (_this.source.duration - _this.sourceStartTime);
619
581
  });
@@ -623,11 +585,9 @@ function AudioSourceMixin(superclass) {
623
585
  var _this = this;
624
586
  _super.prototype.attach.call(this, movie);
625
587
  subscribe(movie, 'movie.seek', function () {
626
- var time = movie.currentTime;
627
- if (time < _this.startTime || time >= _this.startTime + _this.duration) {
588
+ if (_this.currentTime < 0 || _this.currentTime >= _this.duration)
628
589
  return;
629
- }
630
- _this.source.currentTime = time - _this.startTime;
590
+ _this.source.currentTime = _this.currentTime + _this.sourceStartTime;
631
591
  });
632
592
  // TODO: on unattach?
633
593
  subscribe(movie, 'movie.audiodestinationupdate', function (event) {
@@ -639,7 +599,7 @@ function AudioSourceMixin(superclass) {
639
599
  }
640
600
  });
641
601
  // connect to audiocontext
642
- this._audioNode = movie.actx.createMediaElementSource(this.source);
602
+ this._audioNode = this.audioNode || movie.actx.createMediaElementSource(this.source);
643
603
  // Spy on connect and disconnect to remember if it connected to
644
604
  // actx.destination (for Movie#record).
645
605
  var oldConnect = this._audioNode.connect.bind(this.audioNode);
@@ -650,14 +610,16 @@ function AudioSourceMixin(superclass) {
650
610
  var oldDisconnect = this._audioNode.disconnect.bind(this.audioNode);
651
611
  this._audioNode.disconnect = function (destination, output, input) {
652
612
  if (_this._connectedToDestination &&
653
- destination === movie.actx.destination) {
613
+ destination === movie.actx.destination)
654
614
  _this._connectedToDestination = false;
655
- }
656
615
  return oldDisconnect(destination, output, input);
657
616
  };
658
617
  // Connect to actx.destination by default (can be rewired by user)
659
618
  this.audioNode.connect(movie.actx.destination);
660
619
  };
620
+ MixedAudioSource.prototype.detach = function () {
621
+ this.audioNode.disconnect(this.movie.actx.destination);
622
+ };
661
623
  MixedAudioSource.prototype.start = function () {
662
624
  this.source.currentTime = this.currentTime + this.sourceStartTime;
663
625
  this.source.play();
@@ -689,9 +651,8 @@ function AudioSourceMixin(superclass) {
689
651
  },
690
652
  set: function (value) {
691
653
  this._playbackRate = value;
692
- if (this._unstretchedDuration !== undefined) {
654
+ if (this._unstretchedDuration !== undefined)
693
655
  this.duration = this._unstretchedDuration / value;
694
- }
695
656
  },
696
657
  enumerable: false,
697
658
  configurable: true
@@ -771,20 +732,36 @@ var Base = /** @class */ (function () {
771
732
  });
772
733
  return newThis;
773
734
  }
774
- Base.prototype.attach = function (movie) {
735
+ /**
736
+ * Attaches this layer to `movie` if not already attached.
737
+ * @ignore
738
+ */
739
+ Base.prototype.tryAttach = function (movie) {
740
+ if (this._occurrenceCount === 0)
741
+ this.attach(movie);
775
742
  this._occurrenceCount++;
743
+ };
744
+ Base.prototype.attach = function (movie) {
776
745
  this._movie = movie;
777
746
  };
778
- Base.prototype.detach = function () {
779
- if (this.movie === null) {
747
+ /**
748
+ * Dettaches this layer from its movie if the number of times `tryDetach` has
749
+ * been called (including this call) equals the number of times `tryAttach`
750
+ * has been called.
751
+ *
752
+ * @ignore
753
+ */
754
+ Base.prototype.tryDetach = function () {
755
+ if (this.movie === null)
780
756
  throw new Error('No movie to detach from');
781
- }
782
757
  this._occurrenceCount--;
783
758
  // If this layer occurs in another place in a `layers` array, do not unset
784
759
  // _movie. (For calling `unshift` on the `layers` proxy)
785
- if (this._occurrenceCount === 0) {
786
- this._movie = null;
787
- }
760
+ if (this._occurrenceCount === 0)
761
+ this.detach();
762
+ };
763
+ Base.prototype.detach = function () {
764
+ this._movie = null;
788
765
  };
789
766
  /**
790
767
  * Called when the layer is activated
@@ -873,9 +850,8 @@ var Audio = /** @class */ (function (_super) {
873
850
  */
874
851
  function Audio(options) {
875
852
  var _this = _super.call(this, options) || this;
876
- if (_this.duration === undefined) {
853
+ if (_this.duration === undefined)
877
854
  _this.duration = (_this).source.duration - _this.sourceStartTime;
878
- }
879
855
  return _this;
880
856
  }
881
857
  Audio.prototype.getDefaultOptions = function () {
@@ -913,9 +889,8 @@ var Visual = /** @class */ (function (_super) {
913
889
  set: function (target, property, value) {
914
890
  if (!isNaN(Number(property))) {
915
891
  // The property is a number (index)
916
- if (target[property]) {
892
+ if (target[property])
917
893
  target[property].detach();
918
- }
919
894
  value.attach(_this);
920
895
  }
921
896
  target[property] = value;
@@ -928,6 +903,11 @@ var Visual = /** @class */ (function (_super) {
928
903
  * Render visual output
929
904
  */
930
905
  Visual.prototype.render = function () {
906
+ // Prevent empty canvas errors if the width or height is 0
907
+ var width = val(this, 'width', this.currentTime);
908
+ var height = val(this, 'height', this.currentTime);
909
+ if (width === 0 || height === 0)
910
+ return;
931
911
  this.beginRender();
932
912
  this.doRender();
933
913
  this.endRender();
@@ -958,18 +938,16 @@ var Visual = /** @class */ (function (_super) {
958
938
  Visual.prototype.endRender = function () {
959
939
  var w = val(this, 'width', this.currentTime) || val(this.movie, 'width', this.movie.currentTime);
960
940
  var h = val(this, 'height', this.currentTime) || val(this.movie, 'height', this.movie.currentTime);
961
- if (w * h > 0) {
941
+ if (w * h > 0)
962
942
  this._applyEffects();
963
- }
964
943
  // else InvalidStateError for drawing zero-area image in some effects, right?
965
944
  };
966
945
  Visual.prototype._applyEffects = function () {
967
946
  for (var i = 0; i < this.effects.length; i++) {
968
947
  var effect = this.effects[i];
969
- if (effect.enabled) {
948
+ if (effect && effect.enabled)
970
949
  // Pass relative time
971
950
  effect.apply(this, this.movie.currentTime - this.startTime);
972
- }
973
951
  }
974
952
  };
975
953
  /**
@@ -1213,28 +1191,43 @@ var Base$1 = /** @class */ (function () {
1213
1191
  newThis._target = null;
1214
1192
  // Propogate up to target
1215
1193
  subscribe(newThis, 'effect.change.modify', function (event) {
1216
- if (!newThis._target) {
1194
+ if (!newThis._target)
1217
1195
  return;
1218
- }
1219
1196
  var type = newThis._target.type + ".change.effect.modify";
1220
1197
  publish(newThis._target, type, __assign(__assign({}, event), { target: newThis._target, source: newThis, type: type }));
1221
1198
  });
1222
1199
  return newThis;
1223
1200
  }
1224
- Base.prototype.attach = function (target) {
1201
+ /**
1202
+ * Attaches this effect to `target` if not already attached.
1203
+ * @ignore
1204
+ */
1205
+ Base.prototype.tryAttach = function (target) {
1206
+ if (this._occurrenceCount === 0)
1207
+ this.attach(target);
1225
1208
  this._occurrenceCount++;
1226
- this._target = target;
1227
1209
  };
1228
- Base.prototype.detach = function () {
1229
- if (this._target === null) {
1210
+ Base.prototype.attach = function (movie) {
1211
+ this._target = movie;
1212
+ };
1213
+ /**
1214
+ * Dettaches this effect from its target if the number of times `tryDetach`
1215
+ * has been called (including this call) equals the number of times
1216
+ * `tryAttach` has been called.
1217
+ *
1218
+ * @ignore
1219
+ */
1220
+ Base.prototype.tryDetach = function () {
1221
+ if (this._target === null)
1230
1222
  throw new Error('No movie to detach from');
1231
- }
1232
1223
  this._occurrenceCount--;
1233
1224
  // If this effect occurs in another place in the containing array, do not
1234
1225
  // unset _target. (For calling `unshift` on the `layers` proxy)
1235
- if (this._occurrenceCount === 0) {
1236
- this._target = null;
1237
- }
1226
+ if (this._occurrenceCount === 0)
1227
+ this.detach();
1228
+ };
1229
+ Base.prototype.detach = function () {
1230
+ this._target = null;
1238
1231
  };
1239
1232
  // subclasses must implement apply
1240
1233
  /**
@@ -1314,18 +1307,16 @@ var Shader = /** @class */ (function (_super) {
1314
1307
  Shader.prototype._initGl = function () {
1315
1308
  this._canvas = document.createElement('canvas');
1316
1309
  var gl = this._canvas.getContext('webgl');
1317
- if (gl === null) {
1310
+ if (gl === null)
1318
1311
  throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.');
1319
- }
1320
1312
  this._gl = gl;
1321
1313
  return gl;
1322
1314
  };
1323
1315
  Shader.prototype._initTextures = function (userUniforms, userTextures, sourceTextureOptions) {
1324
1316
  var gl = this._gl;
1325
1317
  var maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
1326
- if (userTextures.length > maxTextures) {
1318
+ if (userTextures.length > maxTextures)
1327
1319
  console.warn('Too many textures!');
1328
- }
1329
1320
  this._userTextures = {};
1330
1321
  for (var name_1 in userTextures) {
1331
1322
  var userOptions = userTextures[name_1];
@@ -1338,9 +1329,8 @@ var Shader = /** @class */ (function (_super) {
1338
1329
  * textures, without having to define multiple properties in the effect
1339
1330
  * object.
1340
1331
  */
1341
- if (userUniforms[name_1]) {
1332
+ if (userUniforms[name_1])
1342
1333
  throw new Error("Texture - uniform naming conflict: " + name_1 + "!");
1343
- }
1344
1334
  // Add this as a "user uniform".
1345
1335
  userUniforms[name_1] = '1i'; // texture pointer
1346
1336
  }
@@ -1478,13 +1468,11 @@ var Shader = /** @class */ (function (_super) {
1478
1468
  // Set the shader uniforms.
1479
1469
  // Tell the shader we bound the texture to texture unit 0.
1480
1470
  // All base (Shader class) uniforms are optional.
1481
- if (this._uniformLocations.source) {
1471
+ if (this._uniformLocations.source)
1482
1472
  gl.uniform1i(this._uniformLocations.source, 0);
1483
- }
1484
1473
  // All base (Shader class) uniforms are optional.
1485
- if (this._uniformLocations.size) {
1474
+ if (this._uniformLocations.size)
1486
1475
  gl.uniform2iv(this._uniformLocations.size, [target.canvas.width, target.canvas.height]);
1487
- }
1488
1476
  for (var unprefixed in this._userUniforms) {
1489
1477
  var options = this._userUniforms[unprefixed];
1490
1478
  var value = val(this, unprefixed, reltime);
@@ -1535,40 +1523,35 @@ var Shader = /** @class */ (function (_super) {
1535
1523
  var i = 0;
1536
1524
  for (var name_4 in this._userTextures) {
1537
1525
  var testValue = val(this, name_4, reltime);
1538
- if (value === testValue) {
1526
+ if (value === testValue)
1539
1527
  value = Shader.INTERNAL_TEXTURE_UNITS + i; // after the internal texture units
1540
- }
1541
1528
  i++;
1542
1529
  }
1543
1530
  }
1544
1531
  if (outputType === '3fv') {
1545
1532
  // allow 4-component vectors; TODO: why?
1546
- if (Array.isArray(value) && (value.length === 3 || value.length === 4)) {
1533
+ if (Array.isArray(value) && (value.length === 3 || value.length === 4))
1547
1534
  return value;
1548
- }
1549
1535
  // kind of loose so this can be changed if needed
1550
- if (typeof value === 'object') {
1536
+ if (typeof value === 'object')
1551
1537
  return [
1552
1538
  value.r !== undefined ? value.r : def,
1553
1539
  value.g !== undefined ? value.g : def,
1554
1540
  value.b !== undefined ? value.b : def
1555
1541
  ];
1556
- }
1557
1542
  throw new Error("Invalid type: " + outputType + " or value: " + value);
1558
1543
  }
1559
1544
  if (outputType === '4fv') {
1560
- if (Array.isArray(value) && value.length === 4) {
1545
+ if (Array.isArray(value) && value.length === 4)
1561
1546
  return value;
1562
- }
1563
1547
  // kind of loose so this can be changed if needed
1564
- if (typeof value === 'object') {
1548
+ if (typeof value === 'object')
1565
1549
  return [
1566
1550
  value.r !== undefined ? value.r : def,
1567
1551
  value.g !== undefined ? value.g : def,
1568
1552
  value.b !== undefined ? value.b : def,
1569
1553
  value.a !== undefined ? value.a : def
1570
1554
  ];
1571
- }
1572
1555
  throw new Error("Invalid type: " + outputType + " or value: " + value);
1573
1556
  }
1574
1557
  return value;
@@ -1662,9 +1645,8 @@ var Shader = /** @class */ (function (_super) {
1662
1645
  else {
1663
1646
  // No, it's not a power of 2. Turn off mips and set
1664
1647
  // wrapping to clamp to edge
1665
- if (wrapS !== gl.CLAMP_TO_EDGE || wrapT !== gl.CLAMP_TO_EDGE) {
1648
+ if (wrapS !== gl.CLAMP_TO_EDGE || wrapT !== gl.CLAMP_TO_EDGE)
1666
1649
  console.warn('Wrap mode is not CLAMP_TO_EDGE for a non-power-of-two texture. Defaulting to CLAMP_TO_EDGE');
1667
- }
1668
1650
  gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1669
1651
  gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1670
1652
  }
@@ -1900,6 +1882,7 @@ var Stack = /** @class */ (function (_super) {
1900
1882
  function Stack(options) {
1901
1883
  var _this = _super.call(this) || this;
1902
1884
  _this._effectsBack = [];
1885
+ // TODO: Throw 'change' events in handlers
1903
1886
  _this.effects = new Proxy(_this._effectsBack, {
1904
1887
  deleteProperty: function (target, property) {
1905
1888
  var value = target[property];
@@ -1910,9 +1893,8 @@ var Stack = /** @class */ (function (_super) {
1910
1893
  set: function (target, property, value) {
1911
1894
  // TODO: make sure type check works
1912
1895
  if (!isNaN(Number(property))) { // if property is a number (index)
1913
- if (target[property]) {
1896
+ if (target[property])
1914
1897
  target[property].detach(); // Detach old effect from movie
1915
- }
1916
1898
  value.attach(this._target); // Attach effect to movie
1917
1899
  }
1918
1900
  target[property] = value;
@@ -1921,23 +1903,26 @@ var Stack = /** @class */ (function (_super) {
1921
1903
  });
1922
1904
  options.effects.forEach(function (effect) { return _this.effects.push(effect); });
1923
1905
  return _this;
1906
+ // TODO: Propogate 'change' events from children up
1924
1907
  }
1925
1908
  Stack.prototype.attach = function (movie) {
1926
1909
  _super.prototype.attach.call(this, movie);
1927
- this.effects.forEach(function (effect) {
1910
+ this.effects.filter(function (effect) { return !!effect; }).forEach(function (effect) {
1928
1911
  effect.detach();
1929
1912
  effect.attach(movie);
1930
1913
  });
1931
1914
  };
1932
1915
  Stack.prototype.detach = function () {
1933
1916
  _super.prototype.detach.call(this);
1934
- this.effects.forEach(function (effect) {
1917
+ this.effects.filter(function (effect) { return !!effect; }).forEach(function (effect) {
1935
1918
  effect.detach();
1936
1919
  });
1937
1920
  };
1938
1921
  Stack.prototype.apply = function (target, reltime) {
1939
1922
  for (var i = 0; i < this.effects.length; i++) {
1940
1923
  var effect = this.effects[i];
1924
+ if (!effect)
1925
+ continue;
1941
1926
  effect.apply(target, reltime);
1942
1927
  }
1943
1928
  };
@@ -2001,10 +1986,9 @@ var GaussianBlurComponent = /** @class */ (function (_super) {
2001
1986
  }
2002
1987
  GaussianBlurComponent.prototype.apply = function (target, reltime) {
2003
1988
  var radiusVal = val(this, 'radius', reltime);
2004
- if (radiusVal !== this._radiusCache) {
1989
+ if (radiusVal !== this._radiusCache)
2005
1990
  // Regenerate gaussian distribution canvas.
2006
1991
  this.shape = GaussianBlurComponent._render1DKernel(GaussianBlurComponent._gen1DKernel(radiusVal));
2007
- }
2008
1992
  this._radiusCache = radiusVal;
2009
1993
  _super.prototype.apply.call(this, target, reltime);
2010
1994
  };
@@ -2037,27 +2021,23 @@ var GaussianBlurComponent = /** @class */ (function (_super) {
2037
2021
  var pascal = GaussianBlurComponent._genPascalRow(2 * radius + 1);
2038
2022
  // don't use `reduce` and `map` (overhead?)
2039
2023
  var sum = 0;
2040
- for (var i = 0; i < pascal.length; i++) {
2024
+ for (var i = 0; i < pascal.length; i++)
2041
2025
  sum += pascal[i];
2042
- }
2043
- for (var i = 0; i < pascal.length; i++) {
2026
+ for (var i = 0; i < pascal.length; i++)
2044
2027
  pascal[i] /= sum;
2045
- }
2046
2028
  return pascal;
2047
2029
  };
2048
2030
  GaussianBlurComponent._genPascalRow = function (index) {
2049
- if (index < 0) {
2031
+ if (index < 0)
2050
2032
  throw new Error("Invalid index " + index);
2051
- }
2052
2033
  var currRow = [1];
2053
2034
  for (var i = 1; i < index; i++) {
2054
2035
  var nextRow = [];
2055
2036
  nextRow.length = currRow.length + 1;
2056
2037
  // edges are always 1's
2057
2038
  nextRow[0] = nextRow[nextRow.length - 1] = 1;
2058
- for (var j = 1; j < nextRow.length - 1; j++) {
2039
+ for (var j = 1; j < nextRow.length - 1; j++)
2059
2040
  nextRow[j] = currRow[j - 1] + currRow[j];
2060
- }
2061
2041
  currRow = nextRow;
2062
2042
  }
2063
2043
  return currRow;
@@ -2135,9 +2115,8 @@ var Pixelate = /** @class */ (function (_super) {
2135
2115
  }
2136
2116
  Pixelate.prototype.apply = function (target, reltime) {
2137
2117
  var ps = val(this, 'pixelSize', reltime);
2138
- if (ps % 1 !== 0 || ps < 0) {
2118
+ if (ps % 1 !== 0 || ps < 0)
2139
2119
  throw new Error('Pixel size must be a nonnegative integer');
2140
- }
2141
2120
  _super.prototype.apply.call(this, target, reltime);
2142
2121
  };
2143
2122
  return Pixelate;
@@ -2166,12 +2145,10 @@ var Transform = /** @class */ (function (_super) {
2166
2145
  return _this;
2167
2146
  }
2168
2147
  Transform.prototype.apply = function (target, reltime) {
2169
- if (target.canvas.width !== this._tmpCanvas.width) {
2148
+ if (target.canvas.width !== this._tmpCanvas.width)
2170
2149
  this._tmpCanvas.width = target.canvas.width;
2171
- }
2172
- if (target.canvas.height !== this._tmpCanvas.height) {
2150
+ if (target.canvas.height !== this._tmpCanvas.height)
2173
2151
  this._tmpCanvas.height = target.canvas.height;
2174
- }
2175
2152
  // Use data, since that's the underlying storage
2176
2153
  this._tmpMatrix.data = val(this, 'matrix.data', reltime);
2177
2154
  this._tmpCtx.setTransform(this._tmpMatrix.a, this._tmpMatrix.b, this._tmpMatrix.c, this._tmpMatrix.d, this._tmpMatrix.e, this._tmpMatrix.f);
@@ -2197,9 +2174,8 @@ var Transform = /** @class */ (function (_super) {
2197
2174
  ];
2198
2175
  }
2199
2176
  Matrix.prototype.identity = function () {
2200
- for (var i = 0; i < this.data.length; i++) {
2177
+ for (var i = 0; i < this.data.length; i++)
2201
2178
  this.data[i] = Matrix.IDENTITY.data[i];
2202
- }
2203
2179
  return this;
2204
2180
  };
2205
2181
  /**
@@ -2208,9 +2184,8 @@ var Transform = /** @class */ (function (_super) {
2208
2184
  * @param [val]
2209
2185
  */
2210
2186
  Matrix.prototype.cell = function (x, y, val) {
2211
- if (val !== undefined) {
2187
+ if (val !== undefined)
2212
2188
  this.data[3 * y + x] = val;
2213
- }
2214
2189
  return this.data[3 * y + x];
2215
2190
  };
2216
2191
  Object.defineProperty(Matrix.prototype, "a", {
@@ -2262,19 +2237,16 @@ var Transform = /** @class */ (function (_super) {
2262
2237
  */
2263
2238
  Matrix.prototype.multiply = function (other) {
2264
2239
  // copy to temporary matrix to avoid modifying `this` while reading from it
2265
- for (var x = 0; x < 3; x++) {
2240
+ for (var x = 0; x < 3; x++)
2266
2241
  for (var y = 0; y < 3; y++) {
2267
2242
  var sum = 0;
2268
- for (var i = 0; i < 3; i++) {
2243
+ for (var i = 0; i < 3; i++)
2269
2244
  sum += this.cell(x, i) * other.cell(i, y);
2270
- }
2271
2245
  Matrix._TMP_MATRIX.cell(x, y, sum);
2272
2246
  }
2273
- }
2274
2247
  // copy data from TMP_MATRIX to this
2275
- for (var i = 0; i < Matrix._TMP_MATRIX.data.length; i++) {
2248
+ for (var i = 0; i < Matrix._TMP_MATRIX.data.length; i++)
2276
2249
  this.data[i] = Matrix._TMP_MATRIX.data[i];
2277
- }
2278
2250
  return this;
2279
2251
  };
2280
2252
  /**
@@ -8804,7 +8776,7 @@ var Movie = /** @class */ (function () {
8804
8776
  // Refresh screen when effect is removed, if the movie isn't playing
8805
8777
  // already.
8806
8778
  var value = target[property];
8807
- value.detach();
8779
+ value.tryDetach();
8808
8780
  delete target[property];
8809
8781
  publish(that, 'movie.change.effect.remove', { effect: value });
8810
8782
  return true;
@@ -8816,10 +8788,10 @@ var Movie = /** @class */ (function () {
8816
8788
  publish(that, 'movie.change.effect.remove', {
8817
8789
  effect: target[property]
8818
8790
  });
8819
- target[property].detach();
8791
+ target[property].tryDetach();
8820
8792
  }
8821
8793
  // Attach effect to movie
8822
- value.attach(that);
8794
+ value.tryAttach(that);
8823
8795
  target[property] = value;
8824
8796
  // Refresh screen when effect is set, if the movie isn't playing
8825
8797
  // already.
@@ -8836,12 +8808,11 @@ var Movie = /** @class */ (function () {
8836
8808
  deleteProperty: function (target, property) {
8837
8809
  var oldDuration = this.duration;
8838
8810
  var value = target[property];
8839
- value.detach(that);
8811
+ value.tryDetach(that);
8840
8812
  delete target[property];
8841
8813
  var current = that.currentTime >= value.startTime && that.currentTime < value.startTime + value.duration;
8842
- if (current) {
8814
+ if (current)
8843
8815
  publish(that, 'movie.change.layer.remove', { layer: value });
8844
- }
8845
8816
  publish(that, 'movie.change.duration', { oldDuration: oldDuration });
8846
8817
  return true;
8847
8818
  },
@@ -8853,16 +8824,15 @@ var Movie = /** @class */ (function () {
8853
8824
  publish(that, 'movie.change.layer.remove', {
8854
8825
  layer: target[property]
8855
8826
  });
8856
- target[property].detach();
8827
+ target[property].tryDetach();
8857
8828
  }
8858
8829
  // Attach layer to movie
8859
- value.attach(that);
8830
+ value.tryAttach(that);
8860
8831
  target[property] = value;
8861
8832
  // Refresh screen when a relevant layer is added or removed
8862
8833
  var current = that.currentTime >= value.startTime && that.currentTime < value.startTime + value.duration;
8863
- if (current) {
8834
+ if (current)
8864
8835
  publish(that, 'movie.change.layer.add', { layer: value });
8865
- }
8866
8836
  publish(that, 'movie.change.duration', { oldDuration: oldDuration });
8867
8837
  }
8868
8838
  else {
@@ -8886,14 +8856,12 @@ var Movie = /** @class */ (function () {
8886
8856
  this._lastPlayedOffset = -1;
8887
8857
  // newThis._updateInterval = 0.1; // time in seconds between each "timeupdate" event
8888
8858
  // newThis._lastUpdate = -1;
8889
- if (newThis.autoRefresh) {
8859
+ if (newThis.autoRefresh)
8890
8860
  newThis.refresh(); // render single frame on creation
8891
- }
8892
8861
  // Subscribe to own event "change" (child events propogate up)
8893
8862
  subscribe(newThis, 'movie.change', function () {
8894
- if (newThis.autoRefresh && !newThis.rendering) {
8863
+ if (newThis.autoRefresh && !newThis.rendering)
8895
8864
  newThis.refresh();
8896
- }
8897
8865
  });
8898
8866
  // Subscribe to own event "ended"
8899
8867
  subscribe(newThis, 'movie.recordended', function () {
@@ -8911,16 +8879,14 @@ var Movie = /** @class */ (function () {
8911
8879
  Movie.prototype.play = function () {
8912
8880
  var _this = this;
8913
8881
  return new Promise(function (resolve) {
8914
- if (!_this.paused) {
8882
+ if (!_this.paused)
8915
8883
  throw new Error('Already playing');
8916
- }
8917
8884
  _this._paused = _this._ended = false;
8918
8885
  _this._lastPlayed = performance.now();
8919
8886
  _this._lastPlayedOffset = _this.currentTime;
8920
- if (!_this.renderingFrame) {
8887
+ if (!_this.renderingFrame)
8921
8888
  // Not rendering (and not playing), so play.
8922
8889
  _this._render(true, undefined, resolve);
8923
- }
8924
8890
  // Stop rendering frame if currently doing so, because playing has higher
8925
8891
  // priority. This will effect the next _render call.
8926
8892
  _this._renderingFrame = false;
@@ -8944,12 +8910,13 @@ var Movie = /** @class */ (function () {
8944
8910
  // TODO: improve recording performance to increase frame rate?
8945
8911
  Movie.prototype.record = function (options) {
8946
8912
  var _this = this;
8947
- if (options.video === false && options.audio === false) {
8913
+ if (options.video === false && options.audio === false)
8948
8914
  throw new Error('Both video and audio cannot be disabled');
8949
- }
8950
- if (!this.paused) {
8915
+ if (!this.paused)
8951
8916
  throw new Error('Cannot record movie while already playing or recording');
8952
- }
8917
+ var mimeType = options.type || 'video/webm';
8918
+ if (MediaRecorder && MediaRecorder.isTypeSupported && !MediaRecorder.isTypeSupported(mimeType))
8919
+ throw new Error('Please pass a valid MIME type for the exported video');
8953
8920
  return new Promise(function (resolve, reject) {
8954
8921
  var canvasCache = _this.canvas;
8955
8922
  // Record on a temporary canvas context
@@ -8977,12 +8944,12 @@ var Movie = /** @class */ (function () {
8977
8944
  publish(_this, 'movie.audiodestinationupdate', { movie: _this, destination: audioDestination });
8978
8945
  }
8979
8946
  var stream = new MediaStream(tracks);
8980
- var mediaRecorder = new MediaRecorder(stream, options.mediaRecorderOptions);
8947
+ var mediaRecorderOptions = __assign(__assign({}, (options.mediaRecorderOptions || {})), { mimeType: mimeType });
8948
+ var mediaRecorder = new MediaRecorder(stream, mediaRecorderOptions);
8981
8949
  mediaRecorder.ondataavailable = function (event) {
8982
8950
  // if (this._paused) reject(new Error("Recording was interrupted"));
8983
- if (event.data.size > 0) {
8951
+ if (event.data.size > 0)
8984
8952
  recordedChunks.push(event.data);
8985
- }
8986
8953
  };
8987
8954
  // TODO: publish to movie, not layers
8988
8955
  mediaRecorder.onstop = function () {
@@ -8993,7 +8960,7 @@ var Movie = /** @class */ (function () {
8993
8960
  _this._mediaRecorder = null;
8994
8961
  // Construct the exported video out of all the frame blobs.
8995
8962
  resolve(new Blob(recordedChunks, {
8996
- type: options.type || 'video/webm'
8963
+ type: mimeType
8997
8964
  }));
8998
8965
  };
8999
8966
  mediaRecorder.onerror = reject;
@@ -9011,11 +8978,12 @@ var Movie = /** @class */ (function () {
9011
8978
  Movie.prototype.pause = function () {
9012
8979
  this._paused = true;
9013
8980
  // Deactivate all layers
9014
- for (var i = 0; i < this.layers.length; i++) {
9015
- var layer = this.layers[i];
9016
- layer.stop();
9017
- layer.active = false;
9018
- }
8981
+ for (var i = 0; i < this.layers.length; i++)
8982
+ if (Object.prototype.hasOwnProperty.call(this.layers, i)) {
8983
+ var layer = this.layers[i];
8984
+ layer.stop();
8985
+ layer.active = false;
8986
+ }
9019
8987
  publish(this, 'movie.pause', {});
9020
8988
  return this;
9021
8989
  };
@@ -9041,17 +9009,15 @@ var Movie = /** @class */ (function () {
9041
9009
  if (!this.rendering) {
9042
9010
  // (!this.paused || this._renderingFrame) is true so it's playing or it's
9043
9011
  // rendering a single frame.
9044
- if (done) {
9012
+ if (done)
9045
9013
  done();
9046
- }
9047
9014
  return;
9048
9015
  }
9049
9016
  this._updateCurrentTime(timestamp);
9050
9017
  var recordingEnd = this.recording ? this._recordEndTime : this.duration;
9051
9018
  var recordingEnded = this.currentTime > recordingEnd;
9052
- if (recordingEnded) {
9019
+ if (recordingEnded)
9053
9020
  publish(this, 'movie.recordended', { movie: this });
9054
- }
9055
9021
  // Bad for performance? (remember, it's calling Array.reduce)
9056
9022
  var end = this.duration;
9057
9023
  var ended = this.currentTime > end;
@@ -9066,41 +9032,38 @@ var Movie = /** @class */ (function () {
9066
9032
  if (!this.repeat || this.recording) {
9067
9033
  this._ended = true;
9068
9034
  // Deactivate all layers
9069
- for (var i = 0; i < this.layers.length; i++) {
9070
- var layer = this.layers[i];
9071
- // A layer that has been deleted before layers.length has been updated
9072
- // (see the layers proxy in the constructor).
9073
- if (!layer) {
9074
- continue;
9035
+ for (var i = 0; i < this.layers.length; i++)
9036
+ if (Object.prototype.hasOwnProperty.call(this.layers, i)) {
9037
+ var layer = this.layers[i];
9038
+ // A layer that has been deleted before layers.length has been updated
9039
+ // (see the layers proxy in the constructor).
9040
+ if (!layer)
9041
+ continue;
9042
+ layer.stop();
9043
+ layer.active = false;
9075
9044
  }
9076
- layer.stop();
9077
- layer.active = false;
9078
- }
9079
9045
  }
9080
9046
  }
9081
9047
  // Stop playback or recording if done
9082
9048
  if (recordingEnded || (ended && !this.repeat)) {
9083
- if (done) {
9049
+ if (done)
9084
9050
  done();
9085
- }
9086
9051
  return;
9087
9052
  }
9088
9053
  // Do render
9089
9054
  this._renderBackground(timestamp);
9090
9055
  var frameFullyLoaded = this._renderLayers();
9091
9056
  this._applyEffects();
9092
- if (frameFullyLoaded) {
9057
+ if (frameFullyLoaded)
9093
9058
  publish(this, 'movie.loadeddata', { movie: this });
9094
- }
9095
9059
  // If didn't load in this instant, repeatedly frame-render until frame is
9096
9060
  // loaded.
9097
9061
  // If the expression below is false, don't publish an event, just silently
9098
9062
  // stop render loop.
9099
9063
  if (!repeat || (this._renderingFrame && frameFullyLoaded)) {
9100
9064
  this._renderingFrame = false;
9101
- if (done) {
9065
+ if (done)
9102
9066
  done();
9103
- }
9104
9067
  return;
9105
9068
  }
9106
9069
  window.requestAnimationFrame(function (timestamp) {
@@ -9135,12 +9098,13 @@ var Movie = /** @class */ (function () {
9135
9098
  Movie.prototype._renderLayers = function () {
9136
9099
  var frameFullyLoaded = true;
9137
9100
  for (var i = 0; i < this.layers.length; i++) {
9101
+ if (!Object.prototype.hasOwnProperty.call(this.layers, i))
9102
+ continue;
9138
9103
  var layer = this.layers[i];
9139
9104
  // A layer that has been deleted before layers.length has been updated
9140
9105
  // (see the layers proxy in the constructor).
9141
- if (!layer) {
9106
+ if (!layer)
9142
9107
  continue;
9143
- }
9144
9108
  var reltime = this.currentTime - layer.startTime;
9145
9109
  // Cancel operation if layer disabled or outside layer time interval
9146
9110
  if (!val(layer, 'enabled', reltime) ||
@@ -9162,18 +9126,16 @@ var Movie = /** @class */ (function () {
9162
9126
  layer.active = true;
9163
9127
  }
9164
9128
  // if the layer has an input file
9165
- if ('source' in layer) {
9129
+ if ('source' in layer)
9166
9130
  frameFullyLoaded = frameFullyLoaded && layer.source.readyState >= 2;
9167
- }
9168
9131
  layer.render();
9169
9132
  // if the layer has visual component
9170
9133
  if (layer instanceof Visual) {
9171
9134
  var canvas = layer.canvas;
9172
9135
  // layer.canvas.width and layer.canvas.height should already be interpolated
9173
9136
  // if the layer has an area (else InvalidStateError from canvas)
9174
- if (canvas.width * canvas.height > 0) {
9137
+ if (canvas.width * canvas.height > 0)
9175
9138
  this.cctx.drawImage(canvas, val(layer, 'x', reltime), val(layer, 'y', reltime), canvas.width, canvas.height);
9176
- }
9177
9139
  }
9178
9140
  }
9179
9141
  return frameFullyLoaded;
@@ -9183,9 +9145,8 @@ var Movie = /** @class */ (function () {
9183
9145
  var effect = this.effects[i];
9184
9146
  // An effect that has been deleted before effects.length has been updated
9185
9147
  // (see the effectsproxy in the constructor).
9186
- if (!effect) {
9148
+ if (!effect)
9187
9149
  continue;
9188
- }
9189
9150
  effect.apply(this, this.currentTime);
9190
9151
  }
9191
9152
  };
@@ -9204,9 +9165,9 @@ var Movie = /** @class */ (function () {
9204
9165
  * Convienence method
9205
9166
  */
9206
9167
  Movie.prototype._publishToLayers = function (type, event) {
9207
- for (var i = 0; i < this.layers.length; i++) {
9208
- publish(this.layers[i], type, event);
9209
- }
9168
+ for (var i = 0; i < this.layers.length; i++)
9169
+ if (Object.prototype.hasOwnProperty.call(this.layers, i))
9170
+ publish(this.layers[i], type, event);
9210
9171
  };
9211
9172
  Object.defineProperty(Movie.prototype, "rendering", {
9212
9173
  /**
@@ -9319,13 +9280,11 @@ var Movie = /** @class */ (function () {
9319
9280
  return new Promise(function (resolve, reject) {
9320
9281
  _this._currentTime = time;
9321
9282
  publish(_this, 'movie.seek', {});
9322
- if (refresh) {
9283
+ if (refresh)
9323
9284
  // Pass promise callbacks to `refresh`
9324
9285
  _this.refresh().then(resolve).catch(reject);
9325
- }
9326
- else {
9286
+ else
9327
9287
  resolve();
9328
- }
9329
9288
  });
9330
9289
  };
9331
9290
  Object.defineProperty(Movie.prototype, "canvas", {