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