etro 0.10.1 → 0.12.0

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.
@@ -21,12 +21,25 @@ jobs:
21
21
  - name: Update npm
22
22
  run: |
23
23
  npm i -g npm@^7.x
24
- - name: npm install, lint, build, and test
24
+ - name: Install npm dependencies
25
25
  run: |
26
26
  npm ci
27
27
  node node_modules/puppeteer/install.js
28
- npm run lint
29
- npm run build
30
- xvfb-run --auto-servernum npm test
28
+ env:
29
+ CI: true
30
+ - name: lint code
31
+ run: npm run lint
32
+ env:
33
+ CI: true
34
+ - name: compile project
35
+ run: npm run build
36
+ env:
37
+ CI: true
38
+ - name: run unit tests
39
+ run: xvfb-run --auto-servernum npm run test:unit
40
+ env:
41
+ CI: true
42
+ - name: run smoke tests
43
+ run: xvfb-run --auto-servernum npm run test:smoke
31
44
  env:
32
45
  CI: true
@@ -22,7 +22,10 @@ jobs:
22
22
  else
23
23
  npm install
24
24
  fi
25
- - run: npx shipjs trigger
25
+ - run: |
26
+ git config --global user.email "16855387+clabe45@users.noreply.github.com"
27
+ git config --global user.name "Caleb Sacks"
28
+ npx shipjs trigger
26
29
  env:
27
30
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28
31
  NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npx --no -- commitlint --edit ${1}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npm run lint
5
+
6
+ if [ -z "$SKIP_TESTS" ]; then
7
+ npm run test:unit
8
+ npm run test:smoke
9
+ npm run test:integration
10
+ fi
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # gitmoji as a commit hook
4
+ if npx -v >&/dev/null
5
+ then
6
+ exec < /dev/tty
7
+ npx -c "gitmoji --hook $1 $2"
8
+ else
9
+ exec < /dev/tty
10
+ gitmoji --hook $1 $2
11
+ fi
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [0.12.0] - 2024-01-15
9
+ ### Added
10
+ - `stroke` option for `Text` layer ([#239](https://github.com/etro-js/etro/pull/239)).
11
+
12
+ ### Security
13
+ - Bump @babel/traverse from 7.18.13 to 7.23.2 ([#244](https://github.com/etro-js/etro/pull/244)).
14
+ - Bump browserify-sign from 4.2.1 to 4.2.2 ([#245](https://github.com/etro-js/etro/pull/245)).
15
+ - Bump follow-redirects from 1.14.9 to 1.15.4 ([#247](https://github.com/etro-js/etro/pull/247)).
16
+ - Bump gitmoji-cli from 8.4.0 to 9.0.0.
17
+
18
+ ## [0.11.0] - 2023-08-05
19
+ ### Added
20
+ - `duration` option for `Movie#play` ([#208](https://github.com/etro-js/etro/pull/208)).
21
+
22
+ ### Fixed
23
+ - Audio and video layers going silent after the first time recording the movie ([#106](https://github.com/etro-js/etro/issues/106)).
24
+ - `Failed to set the 'currentTime' property on 'HTMLMediaElement'` error when seeking audio and video layers ([#227](https://github.com/etro-js/etro/pull/227)).
25
+ - Seeking while playing not updating the movie's current time ([#233](https://github.com/etro-js/etro/issues/233)).
26
+
27
+ ### Security
28
+ - Bump word-wrap from 1.2.3 to 1.2.5 ([#222](https://github.com/etro-js/etro/pull/222)).
29
+
8
30
  ## [0.10.1] - 2023-07-16
9
31
  ### Security
10
32
  - Bump engine.io and socket.io.
@@ -280,6 +302,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
280
302
  - Gaussian blur
281
303
  - Transform
282
304
 
305
+ [0.12.0]: https://github.com/etro-js/etro/compare/v0.11.0...v0.12.0
306
+ [0.11.0]: https://github.com/etro-js/etro/compare/v0.10.1...v0.11.0
283
307
  [0.10.1]: https://github.com/etro-js/etro/compare/v0.10.0...v0.10.1
284
308
  [0.10.0]: https://github.com/etro-js/etro/compare/v0.9.1...v0.10.0
285
309
  [0.9.1]: https://github.com/etro-js/etro/compare/v0.9.0...v0.9.1
package/CONTRIBUTING.md CHANGED
@@ -10,83 +10,58 @@ Thank you for considering contributing to Etro! There are many ways you can cont
10
10
 
11
11
  ## Setting up your local environment
12
12
 
13
- #### Step 0: Dependencies
14
-
15
13
  - You will need Git, Node, NPM (at least 7.x) and Firefox (for headless functional testing) installed.
16
-
17
- #### Step 1: Fork
18
-
19
- - Create your own fork of Etro. Then run
20
-
14
+ - To get started, create your own fork of Etro. Then run
21
15
  ```
22
16
  git clone https://github.com/YOUR_USERNAME/etro.git
23
17
  cd etro
24
18
  npm install
25
- npm test
19
+ npm run test:unit
20
+ npm run test:smoke
21
+ npm run test:integration
26
22
  ```
27
23
 
28
24
  ## Making your changes
29
25
 
30
- #### Step 2: Code
31
-
32
26
  - Make some changes and update tests
33
27
  - If you are writing code, the linter uses [StandardJS](https://standardjs.com/rules.html) for style conventions
34
28
  - If you're adding or updating an effect:
35
29
  - Add your effect to **scripts/gen-effect-samples.html**
36
30
  - Run `npm run effects`
37
31
  - Briefly review the images in **spec/integration/assets/effect/**
38
- - When you're ready to submit, first run
32
+ - As you work, you can run
39
33
  ```
40
- npm run lint
34
+ npm run fix
41
35
  npm run build
42
- npm test
36
+ npm run test:unit
37
+ npm run test:smoke
38
+ npm run test:integration
43
39
  ```
44
40
 
45
- to lint the code, generate the [dist](dist) files and run unit tests on them. It's helpful to put these commands in a pre-commit hook.
41
+ to lint and compile the code and run the tests on them. Husky will run these commands automatically when you commit.
46
42
 
47
- #### Step 3: Commit
48
-
49
- - Please follow these commit message guidelines:
50
- - Optionally, prefix each commit message with [an appropriate emoji](https://gitmoji.dev), such as `:bug:` for fixes.
51
- - Write in the imperative tense
52
- - Wrap lines after 72 characters (for Vim add `filetype indent plugin on` to ~/.vimrc, it's enabled by default in Atom).
53
- - Format:
54
- ```
55
- :emoji: One-liner
56
-
57
- Optional description
58
- ```
43
+ - Please commit to a new branch, not master
59
44
 
60
45
  ## Submitting your changes
61
46
 
62
- #### Step 4: Push
63
-
64
- - First, rebase (please avoid merging) to integrate your work with any new changes in the main repository
65
-
47
+ - Before pushing to your fork, rebase (please avoid merging) to integrate your work with any new changes in the main repository
66
48
  ```
67
49
  git fetch upstream
68
50
  git rebase upstream/master
69
51
  ```
70
-
71
- - Push to the fork
72
-
73
- #### Step 5: Pull request
74
-
75
- - Open a pull request from the branch in your fork to the main repository
76
- - If you changed any core functionality, make sure you explain your motives for those changes
77
-
78
- #### Step 6: Feedback
79
-
52
+ - Open a pull request from the branch in your fork to the main repository. If you changed any core functionality, make sure you explain your motives for those changes
80
53
  - A large part of the submission process is receiving feedback on how you can improve you pull request. If you need to change your pull request, feel free to push more commits.
81
54
 
82
- ## Code overview
55
+ ## Tests
83
56
 
84
- ### Etro Overview
57
+ Automated tests are run with [KarmaJS](https://karma-runner.github.io/) in a headless Firefox session. Unit tests validate the logic of the code in etro, with the DOM and any other external dependencies mocked. Because audio cannot be captured in the GitHub Actions runner, the end-to-end tests are divided into two suites. All end-to-end tests that need to validate audio output should be placed in **spec/integration/**. All end-to-end tests that do **not** require an audio device should be placed in **spec/smoke/**. The integration tests can only be run locally, but the other two suites can be run anywhere.
85
58
 
86
- Check out [the user docs](https://etrojs.dev/docs/intro) for a high-level overview of Etro.
59
+ ## Code overview
87
60
 
88
61
  ### Events
89
62
 
63
+ > Events were deprecated in v0.10.0 in favor of async methods with callbacks.
64
+
90
65
  Events emitted by Etro objects use a [pub/sub system](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern). To emit an event, use `event.publish(target, type, event)`. For instance,
91
66
 
92
67
  ```js
package/README.md CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  [![](https://img.shields.io/npm/v/etro)](https://www.npmjs.com/package/etro)
4
4
  [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fetro-js%2Fetro%2Fbadge&style=flat)](https://actions-badge.atrox.dev/etro-js/etro/goto)
5
+ [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/myrBsQ8Cht)
5
6
 
6
7
  Etro is a typescript framework for programmatically editing videos. It lets you
7
8
  composite layers and add filters (effects). Etro comes shipped with text, video,
8
9
  audio and image layers, along with a bunch of GLSL effects. You can also define
9
10
  your own layers and effects with javascript and GLSL.
10
11
 
11
- [Join our Discord](https://discord.gg/myrBsQ8Cht)
12
-
13
12
  ## Features
14
13
 
15
14
  - Composite video and audio layers
@@ -0,0 +1,39 @@
1
+ import type {UserConfig} from '@commitlint/types';
2
+ import { RuleConfigSeverity } from "@commitlint/types";
3
+
4
+ const Configuration: UserConfig = {
5
+ /*
6
+ * Resolve and load @commitlint/format from node_modules.
7
+ * Referenced package must be installed
8
+ */
9
+ formatter: '@commitlint/format',
10
+ /*
11
+ * Any rules defined here will override the default ones
12
+ */
13
+ rules: {
14
+ 'header-max-length': [RuleConfigSeverity.Error, 'always', 72],
15
+ 'body-max-line-length': [RuleConfigSeverity.Error, 'always', 72],
16
+ },
17
+ /*
18
+ * Functions that return true if commitlint should ignore the given message.
19
+ */
20
+ ignores: [(commit) => commit === ''],
21
+ /*
22
+ * Whether commitlint uses the default ignore rules.
23
+ */
24
+ defaultIgnores: true,
25
+ /*
26
+ * Custom URL to show upon failure
27
+ */
28
+ helpUrl:
29
+ 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
30
+ /*
31
+ * Custom prompt configs
32
+ */
33
+ prompt: {
34
+ messages: {},
35
+ questions: {},
36
+ },
37
+ };
38
+
39
+ module.exports = Configuration;
package/dist/etro-cjs.js CHANGED
@@ -667,9 +667,11 @@ function AudioSourceMixin(superclass) {
667
667
  _this._sourceStartTime = options.sourceStartTime || 0;
668
668
  applyOptions(options, _this);
669
669
  var load = function () {
670
- // TODO: && ?
671
- if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0) {
672
- throw new Error('Invalid options.duration or options.sourceStartTime');
670
+ if (options.duration < 0) {
671
+ throw new Error('Invalid options.duration. It must be a non-negative value.');
672
+ }
673
+ if (_this.sourceStartTime > _this.source.duration) {
674
+ throw new Error('options.sourceStartTime cannot exceed options.source.duration');
673
675
  }
674
676
  _this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
675
677
  _this.duration = _this._unstretchedDuration / (_this.playbackRate);
@@ -715,32 +717,15 @@ function AudioSourceMixin(superclass) {
715
717
  _super.prototype.attach.call(this, movie);
716
718
  // TODO: on unattach?
717
719
  subscribe(movie, 'audiodestinationupdate', function (event) {
718
- // Connect to new destination if immediately connected to the existing
719
- // destination.
720
- if (_this._connectedToDestination) {
721
- _this.audioNode.disconnect(movie.actx.destination);
722
- _this.audioNode.connect(event.destination);
723
- }
720
+ _this.audioNode.disconnect(_this._lastAudioDestination);
721
+ _this.audioNode.connect(event.destination);
722
+ _this._lastAudioDestination = event.destination;
724
723
  });
725
724
  // connect to audiocontext
726
725
  this._audioNode = this.audioNode || movie.actx.createMediaElementSource(this.source);
727
- // Spy on connect and disconnect to remember if it connected to
728
- // actx.destination (for Movie#record).
729
- var oldConnect = this._audioNode.connect.bind(this.audioNode);
730
- this._audioNode.connect = function (destination, outputIndex, inputIndex) {
731
- _this._connectedToDestination = destination === movie.actx.destination;
732
- return oldConnect(destination, outputIndex, inputIndex);
733
- };
734
- var oldDisconnect = this._audioNode.disconnect.bind(this.audioNode);
735
- this._audioNode.disconnect = function (destination, output, input) {
736
- if (_this._connectedToDestination &&
737
- destination === movie.actx.destination) {
738
- _this._connectedToDestination = false;
739
- }
740
- return oldDisconnect(destination, output, input);
741
- };
742
726
  // Connect to actx.destination by default (can be rewired by user)
743
727
  this.audioNode.connect(movie.actx.destination);
728
+ this._lastAudioDestination = movie.actx.destination;
744
729
  };
745
730
  MixedAudioSource.prototype.detach = function () {
746
731
  // Cache dest before super.detach() unsets this.movie
@@ -754,7 +739,12 @@ function AudioSourceMixin(superclass) {
754
739
  };
755
740
  MixedAudioSource.prototype.seek = function (time) {
756
741
  _super.prototype.seek.call(this, time);
757
- this.source.currentTime = this.currentTime + this.sourceStartTime;
742
+ if (isNaN(this.currentTime)) {
743
+ this.source.currentTime = this.sourceStartTime;
744
+ }
745
+ else {
746
+ this.source.currentTime = this.currentTime + this.sourceStartTime;
747
+ }
758
748
  };
759
749
  MixedAudioSource.prototype.render = function () {
760
750
  _super.prototype.render.call(this);
@@ -1444,6 +1434,12 @@ var Image = /** @class */ (function (_super) {
1444
1434
  return Image;
1445
1435
  }(VisualSourceMixin(Visual)));
1446
1436
 
1437
+ var TextStrokePosition;
1438
+ (function (TextStrokePosition) {
1439
+ TextStrokePosition[TextStrokePosition["Inside"] = 0] = "Inside";
1440
+ TextStrokePosition[TextStrokePosition["Center"] = 1] = "Center";
1441
+ TextStrokePosition[TextStrokePosition["Outside"] = 2] = "Outside";
1442
+ })(TextStrokePosition || (TextStrokePosition = {}));
1447
1443
  var Text = /** @class */ (function (_super) {
1448
1444
  __extends(Text, _super);
1449
1445
  /**
@@ -1468,6 +1464,7 @@ var Text = /** @class */ (function (_super) {
1468
1464
  // this._prevMaxWidth = undefined;
1469
1465
  }
1470
1466
  Text.prototype.doRender = function () {
1467
+ var _a, _b;
1471
1468
  _super.prototype.doRender.call(this);
1472
1469
  var text = val(this, 'text', this.currentTime);
1473
1470
  var font = val(this, 'font', this.currentTime);
@@ -1481,6 +1478,28 @@ var Text = /** @class */ (function (_super) {
1481
1478
  this.cctx.textBaseline = val(this, 'textBaseline', this.currentTime);
1482
1479
  this.cctx.direction = val(this, 'textDirection', this.currentTime);
1483
1480
  this.cctx.fillText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1481
+ var textStroke = val(this, 'textStroke', this.currentTime);
1482
+ if (textStroke) {
1483
+ this.cctx.strokeStyle = textStroke.color;
1484
+ this.cctx.lineWidth = (_a = textStroke.thickness) !== null && _a !== void 0 ? _a : 1;
1485
+ var position = (_b = textStroke.position) !== null && _b !== void 0 ? _b : 'outer';
1486
+ // Save the globalCompositeOperation, we have to revert it after stroking the text.
1487
+ var globalCompositionOperation = this.cctx.globalCompositeOperation;
1488
+ switch (position) {
1489
+ case TextStrokePosition.Inside:
1490
+ this.cctx.globalCompositeOperation = 'source-atop';
1491
+ this.cctx.lineWidth *= 2;
1492
+ break;
1493
+ case TextStrokePosition.Center:
1494
+ break;
1495
+ case TextStrokePosition.Outside:
1496
+ this.cctx.globalCompositeOperation = 'destination-over';
1497
+ this.cctx.lineWidth *= 2;
1498
+ break;
1499
+ }
1500
+ this.cctx.strokeText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1501
+ this.cctx.globalCompositeOperation = globalCompositionOperation;
1502
+ }
1484
1503
  this._prevText = text;
1485
1504
  this._prevFont = font;
1486
1505
  this._prevMaxWidth = maxWidth;
@@ -1509,7 +1528,7 @@ var Text = /** @class */ (function (_super) {
1509
1528
  * @deprecated See {@link https://github.com/etro-js/etro/issues/131}
1510
1529
  */
1511
1530
  Text.prototype.getDefaultOptions = function () {
1512
- return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr' });
1531
+ return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr', textStroke: null });
1513
1532
  };
1514
1533
  return Text;
1515
1534
  }(Visual));
@@ -1547,6 +1566,7 @@ var index = /*#__PURE__*/Object.freeze({
1547
1566
  Base: Base,
1548
1567
  Image: Image,
1549
1568
  Text: Text,
1569
+ get TextStrokePosition () { return TextStrokePosition; },
1550
1570
  Video: Video,
1551
1571
  VisualSourceMixin: VisualSourceMixin,
1552
1572
  Visual: Visual
@@ -2843,10 +2863,6 @@ var Movie = /** @class */ (function () {
2843
2863
  // `render`). It's only valid while rendering.
2844
2864
  this._renderingFrame = false;
2845
2865
  this.currentTime = 0;
2846
- // The last time `play` was called, -1 works well in comparisons
2847
- this._lastPlayed = -1;
2848
- // What `currentTime` was when `play` was called
2849
- this._lastPlayedOffset = -1;
2850
2866
  }
2851
2867
  Movie.prototype._whenReady = function () {
2852
2868
  return __awaiter(this, void 0, void 0, function () {
@@ -2868,6 +2884,7 @@ var Movie = /** @class */ (function () {
2868
2884
  *
2869
2885
  * @param [options]
2870
2886
  * @param [options.onStart] Called when the movie starts playing
2887
+ * @param [options.duration] The duration of the movie to play in seconds
2871
2888
  *
2872
2889
  * @return Fulfilled when the movie is done playing, never fails
2873
2890
  */
@@ -2885,8 +2902,8 @@ var Movie = /** @class */ (function () {
2885
2902
  throw new Error('Already playing');
2886
2903
  }
2887
2904
  this._paused = this._ended = false;
2888
- this._lastPlayed = performance.now();
2889
- this._lastPlayedOffset = this.currentTime;
2905
+ this._lastRealTime = performance.now();
2906
+ this._endTime = options.duration ? this.currentTime + options.duration : this.duration;
2890
2907
  (_a = options.onStart) === null || _a === void 0 ? void 0 : _a.call(options);
2891
2908
  // For backwards compatibility
2892
2909
  publish(this, 'movie.play', {});
@@ -2894,15 +2911,19 @@ var Movie = /** @class */ (function () {
2894
2911
  return [4 /*yield*/, new Promise(function (resolve) {
2895
2912
  if (!_this.renderingFrame) {
2896
2913
  // Not rendering (and not playing), so play.
2897
- _this._render(true, undefined, resolve);
2914
+ _this._render(undefined, resolve);
2898
2915
  }
2899
2916
  // Stop rendering frame if currently doing so, because playing has higher
2900
2917
  // priority. This will affect the next _render call.
2901
2918
  _this._renderingFrame = false;
2902
- })];
2919
+ })
2920
+ // After we're done playing, clear the last timestamp
2921
+ ];
2903
2922
  case 2:
2904
2923
  // Repeatedly render frames until the movie ends
2905
2924
  _b.sent();
2925
+ // After we're done playing, clear the last timestamp
2926
+ this._lastRealTime = undefined;
2906
2927
  return [2 /*return*/];
2907
2928
  }
2908
2929
  });
@@ -2976,16 +2997,17 @@ var Movie = /** @class */ (function () {
2976
2997
  // Create the stream
2977
2998
  this._currentStream = new MediaStream(tracks);
2978
2999
  // Play the movie
2979
- this._endTime = options.duration ? this.currentTime + options.duration : this.duration;
2980
3000
  return [4 /*yield*/, this.play({
2981
3001
  onStart: function () {
2982
3002
  // Call the user's onStart callback
2983
3003
  options.onStart(_this._currentStream);
2984
- }
3004
+ },
3005
+ duration: options.duration
2985
3006
  })
2986
3007
  // Clear the stream after the movie is done playing
2987
3008
  ];
2988
3009
  case 2:
3010
+ // Play the movie
2989
3011
  _a.sent();
2990
3012
  // Clear the stream after the movie is done playing
2991
3013
  this._currentStream.getTracks().forEach(function (track) {
@@ -3120,11 +3142,13 @@ var Movie = /** @class */ (function () {
3120
3142
  return this;
3121
3143
  };
3122
3144
  /**
3145
+ * Processes one frame of the movie and draws it to the canvas
3146
+ *
3123
3147
  * @param [timestamp=performance.now()]
3124
3148
  * @param [done=undefined] - Called when done playing or when the current
3125
3149
  * frame is loaded
3126
3150
  */
3127
- Movie.prototype._render = function (repeat, timestamp, done) {
3151
+ Movie.prototype._render = function (timestamp, done) {
3128
3152
  var _this = this;
3129
3153
  if (timestamp === void 0) { timestamp = performance.now(); }
3130
3154
  if (done === void 0) { done = undefined; }
@@ -3161,8 +3185,6 @@ var Movie = /** @class */ (function () {
3161
3185
  // value and publish a 'imeupdate' event.
3162
3186
  this._currentTime = 0;
3163
3187
  publish(this, 'movie.timeupdate', { movie: this });
3164
- this._lastPlayed = performance.now();
3165
- this._lastPlayedOffset = 0; // this.currentTime
3166
3188
  this._renderingFrame = false;
3167
3189
  // Stop playback or recording if done (except if it's playing and repeat
3168
3190
  // is true)
@@ -3214,18 +3236,19 @@ var Movie = /** @class */ (function () {
3214
3236
  }
3215
3237
  // TODO: Is making a new arrow function every frame bad for performance?
3216
3238
  window.requestAnimationFrame(function () {
3217
- _this._render(repeat, undefined, done);
3239
+ _this._render(undefined, done);
3218
3240
  });
3219
3241
  };
3220
3242
  Movie.prototype._updateCurrentTime = function (timestampMs, end) {
3221
3243
  // If we're only frame-rendering (current frame only), it doesn't matter if
3222
3244
  // it's paused or not.
3223
3245
  if (!this._renderingFrame) {
3224
- var sinceLastPlayed = (timestampMs - this._lastPlayed) / 1000;
3225
- var currentTime = this._lastPlayedOffset + sinceLastPlayed;
3226
- if (this.currentTime !== currentTime) {
3246
+ var timestamp = timestampMs / 1000;
3247
+ var delta = timestamp - this._lastRealTime;
3248
+ this._lastRealTime = timestamp;
3249
+ if (delta > 0) {
3227
3250
  // Update the current time (don't use setter)
3228
- this._currentTime = currentTime;
3251
+ this._currentTime += delta;
3229
3252
  // For backwards compatibility, publish a 'movie.timeupdate' event.
3230
3253
  publish(this, 'movie.timeupdate', { movie: this });
3231
3254
  }
@@ -3234,6 +3257,11 @@ var Movie = /** @class */ (function () {
3234
3257
  }
3235
3258
  }
3236
3259
  };
3260
+ /**
3261
+ * Draws the movie's background to the canvas
3262
+ *
3263
+ * @param timestamp The current high-resolution timestamp in milliseconds
3264
+ */
3237
3265
  Movie.prototype._renderBackground = function (timestamp) {
3238
3266
  this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
3239
3267
  // Evaluate background color (since it's a dynamic property)
@@ -3244,7 +3272,7 @@ var Movie = /** @class */ (function () {
3244
3272
  }
3245
3273
  };
3246
3274
  /**
3247
- * @param [timestamp=performance.now()]
3275
+ * Ticks all layers and renders them to the canvas
3248
3276
  */
3249
3277
  Movie.prototype._renderLayers = function () {
3250
3278
  for (var i = 0; i < this.layers.length; i++) {
@@ -3289,6 +3317,12 @@ var Movie = /** @class */ (function () {
3289
3317
  }
3290
3318
  }
3291
3319
  };
3320
+ /**
3321
+ * Applies all of the movie's effects to the canvas
3322
+ *
3323
+ * Note: This method only applies the movie's effects, not the layers'
3324
+ * effects.
3325
+ */
3292
3326
  Movie.prototype._applyEffects = function () {
3293
3327
  for (var i = 0; i < this.effects.length; i++) {
3294
3328
  var effect = this.effects[i];
@@ -3315,7 +3349,7 @@ var Movie = /** @class */ (function () {
3315
3349
  }
3316
3350
  return new Promise(function (resolve) {
3317
3351
  _this._renderingFrame = true;
3318
- _this._render(false, undefined, resolve);
3352
+ _this._render(undefined, resolve);
3319
3353
  });
3320
3354
  };
3321
3355
  /**
@@ -3364,7 +3398,7 @@ var Movie = /** @class */ (function () {
3364
3398
  *
3365
3399
  * Calculated from the end time of the last layer
3366
3400
  */
3367
- // TODO: dirty flag?
3401
+ // TODO: cache
3368
3402
  get: function () {
3369
3403
  return this.layers.reduce(function (end, layer) { return Math.max(layer.startTime + layer.duration, end); }, 0);
3370
3404
  },
@@ -3373,6 +3407,7 @@ var Movie = /** @class */ (function () {
3373
3407
  });
3374
3408
  /**
3375
3409
  * Convenience method for `layers.push()`
3410
+ *
3376
3411
  * @param layer
3377
3412
  * @return The movie
3378
3413
  */
@@ -3382,6 +3417,7 @@ var Movie = /** @class */ (function () {
3382
3417
  };
3383
3418
  /**
3384
3419
  * Convenience method for `effects.push()`
3420
+ *
3385
3421
  * @param effect
3386
3422
  * @return the movie
3387
3423
  */