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.
- package/.github/workflows/nodejs.yml +17 -4
- package/.github/workflows/shipjs-trigger.yml +4 -1
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +10 -0
- package/.husky/prepare-commit-msg +11 -0
- package/CHANGELOG.md +24 -0
- package/CONTRIBUTING.md +18 -43
- package/README.md +1 -2
- package/commitlint.config.ts +39 -0
- package/dist/etro-cjs.js +83 -47
- package/dist/etro-iife.js +83 -47
- package/dist/layer/text.d.ts +16 -1
- package/dist/movie/movie.d.ts +20 -3
- package/karma.conf.js +10 -3
- package/package.json +14 -6
- package/scripts/effect/save-effect-samples.js +1 -1
- package/ship.config.js +12 -1
- package/src/layer/audio-source.ts +17 -28
- package/src/layer/text.ts +48 -2
- package/src/movie/movie.ts +38 -24
package/dist/etro-iife.js
CHANGED
|
@@ -668,9 +668,11 @@ var etro = (function () {
|
|
|
668
668
|
_this._sourceStartTime = options.sourceStartTime || 0;
|
|
669
669
|
applyOptions(options, _this);
|
|
670
670
|
var load = function () {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
671
|
+
if (options.duration < 0) {
|
|
672
|
+
throw new Error('Invalid options.duration. It must be a non-negative value.');
|
|
673
|
+
}
|
|
674
|
+
if (_this.sourceStartTime > _this.source.duration) {
|
|
675
|
+
throw new Error('options.sourceStartTime cannot exceed options.source.duration');
|
|
674
676
|
}
|
|
675
677
|
_this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
|
|
676
678
|
_this.duration = _this._unstretchedDuration / (_this.playbackRate);
|
|
@@ -716,32 +718,15 @@ var etro = (function () {
|
|
|
716
718
|
_super.prototype.attach.call(this, movie);
|
|
717
719
|
// TODO: on unattach?
|
|
718
720
|
subscribe(movie, 'audiodestinationupdate', function (event) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
_this.audioNode.disconnect(movie.actx.destination);
|
|
723
|
-
_this.audioNode.connect(event.destination);
|
|
724
|
-
}
|
|
721
|
+
_this.audioNode.disconnect(_this._lastAudioDestination);
|
|
722
|
+
_this.audioNode.connect(event.destination);
|
|
723
|
+
_this._lastAudioDestination = event.destination;
|
|
725
724
|
});
|
|
726
725
|
// connect to audiocontext
|
|
727
726
|
this._audioNode = this.audioNode || movie.actx.createMediaElementSource(this.source);
|
|
728
|
-
// Spy on connect and disconnect to remember if it connected to
|
|
729
|
-
// actx.destination (for Movie#record).
|
|
730
|
-
var oldConnect = this._audioNode.connect.bind(this.audioNode);
|
|
731
|
-
this._audioNode.connect = function (destination, outputIndex, inputIndex) {
|
|
732
|
-
_this._connectedToDestination = destination === movie.actx.destination;
|
|
733
|
-
return oldConnect(destination, outputIndex, inputIndex);
|
|
734
|
-
};
|
|
735
|
-
var oldDisconnect = this._audioNode.disconnect.bind(this.audioNode);
|
|
736
|
-
this._audioNode.disconnect = function (destination, output, input) {
|
|
737
|
-
if (_this._connectedToDestination &&
|
|
738
|
-
destination === movie.actx.destination) {
|
|
739
|
-
_this._connectedToDestination = false;
|
|
740
|
-
}
|
|
741
|
-
return oldDisconnect(destination, output, input);
|
|
742
|
-
};
|
|
743
727
|
// Connect to actx.destination by default (can be rewired by user)
|
|
744
728
|
this.audioNode.connect(movie.actx.destination);
|
|
729
|
+
this._lastAudioDestination = movie.actx.destination;
|
|
745
730
|
};
|
|
746
731
|
MixedAudioSource.prototype.detach = function () {
|
|
747
732
|
// Cache dest before super.detach() unsets this.movie
|
|
@@ -755,7 +740,12 @@ var etro = (function () {
|
|
|
755
740
|
};
|
|
756
741
|
MixedAudioSource.prototype.seek = function (time) {
|
|
757
742
|
_super.prototype.seek.call(this, time);
|
|
758
|
-
|
|
743
|
+
if (isNaN(this.currentTime)) {
|
|
744
|
+
this.source.currentTime = this.sourceStartTime;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
this.source.currentTime = this.currentTime + this.sourceStartTime;
|
|
748
|
+
}
|
|
759
749
|
};
|
|
760
750
|
MixedAudioSource.prototype.render = function () {
|
|
761
751
|
_super.prototype.render.call(this);
|
|
@@ -1445,6 +1435,12 @@ var etro = (function () {
|
|
|
1445
1435
|
return Image;
|
|
1446
1436
|
}(VisualSourceMixin(Visual)));
|
|
1447
1437
|
|
|
1438
|
+
var TextStrokePosition;
|
|
1439
|
+
(function (TextStrokePosition) {
|
|
1440
|
+
TextStrokePosition[TextStrokePosition["Inside"] = 0] = "Inside";
|
|
1441
|
+
TextStrokePosition[TextStrokePosition["Center"] = 1] = "Center";
|
|
1442
|
+
TextStrokePosition[TextStrokePosition["Outside"] = 2] = "Outside";
|
|
1443
|
+
})(TextStrokePosition || (TextStrokePosition = {}));
|
|
1448
1444
|
var Text = /** @class */ (function (_super) {
|
|
1449
1445
|
__extends(Text, _super);
|
|
1450
1446
|
/**
|
|
@@ -1469,6 +1465,7 @@ var etro = (function () {
|
|
|
1469
1465
|
// this._prevMaxWidth = undefined;
|
|
1470
1466
|
}
|
|
1471
1467
|
Text.prototype.doRender = function () {
|
|
1468
|
+
var _a, _b;
|
|
1472
1469
|
_super.prototype.doRender.call(this);
|
|
1473
1470
|
var text = val(this, 'text', this.currentTime);
|
|
1474
1471
|
var font = val(this, 'font', this.currentTime);
|
|
@@ -1482,6 +1479,28 @@ var etro = (function () {
|
|
|
1482
1479
|
this.cctx.textBaseline = val(this, 'textBaseline', this.currentTime);
|
|
1483
1480
|
this.cctx.direction = val(this, 'textDirection', this.currentTime);
|
|
1484
1481
|
this.cctx.fillText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
|
|
1482
|
+
var textStroke = val(this, 'textStroke', this.currentTime);
|
|
1483
|
+
if (textStroke) {
|
|
1484
|
+
this.cctx.strokeStyle = textStroke.color;
|
|
1485
|
+
this.cctx.lineWidth = (_a = textStroke.thickness) !== null && _a !== void 0 ? _a : 1;
|
|
1486
|
+
var position = (_b = textStroke.position) !== null && _b !== void 0 ? _b : 'outer';
|
|
1487
|
+
// Save the globalCompositeOperation, we have to revert it after stroking the text.
|
|
1488
|
+
var globalCompositionOperation = this.cctx.globalCompositeOperation;
|
|
1489
|
+
switch (position) {
|
|
1490
|
+
case TextStrokePosition.Inside:
|
|
1491
|
+
this.cctx.globalCompositeOperation = 'source-atop';
|
|
1492
|
+
this.cctx.lineWidth *= 2;
|
|
1493
|
+
break;
|
|
1494
|
+
case TextStrokePosition.Center:
|
|
1495
|
+
break;
|
|
1496
|
+
case TextStrokePosition.Outside:
|
|
1497
|
+
this.cctx.globalCompositeOperation = 'destination-over';
|
|
1498
|
+
this.cctx.lineWidth *= 2;
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
this.cctx.strokeText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
|
|
1502
|
+
this.cctx.globalCompositeOperation = globalCompositionOperation;
|
|
1503
|
+
}
|
|
1485
1504
|
this._prevText = text;
|
|
1486
1505
|
this._prevFont = font;
|
|
1487
1506
|
this._prevMaxWidth = maxWidth;
|
|
@@ -1510,7 +1529,7 @@ var etro = (function () {
|
|
|
1510
1529
|
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1511
1530
|
*/
|
|
1512
1531
|
Text.prototype.getDefaultOptions = function () {
|
|
1513
|
-
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' });
|
|
1532
|
+
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 });
|
|
1514
1533
|
};
|
|
1515
1534
|
return Text;
|
|
1516
1535
|
}(Visual));
|
|
@@ -1548,6 +1567,7 @@ var etro = (function () {
|
|
|
1548
1567
|
Base: Base,
|
|
1549
1568
|
Image: Image,
|
|
1550
1569
|
Text: Text,
|
|
1570
|
+
get TextStrokePosition () { return TextStrokePosition; },
|
|
1551
1571
|
Video: Video,
|
|
1552
1572
|
VisualSourceMixin: VisualSourceMixin,
|
|
1553
1573
|
Visual: Visual
|
|
@@ -2844,10 +2864,6 @@ var etro = (function () {
|
|
|
2844
2864
|
// `render`). It's only valid while rendering.
|
|
2845
2865
|
this._renderingFrame = false;
|
|
2846
2866
|
this.currentTime = 0;
|
|
2847
|
-
// The last time `play` was called, -1 works well in comparisons
|
|
2848
|
-
this._lastPlayed = -1;
|
|
2849
|
-
// What `currentTime` was when `play` was called
|
|
2850
|
-
this._lastPlayedOffset = -1;
|
|
2851
2867
|
}
|
|
2852
2868
|
Movie.prototype._whenReady = function () {
|
|
2853
2869
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -2869,6 +2885,7 @@ var etro = (function () {
|
|
|
2869
2885
|
*
|
|
2870
2886
|
* @param [options]
|
|
2871
2887
|
* @param [options.onStart] Called when the movie starts playing
|
|
2888
|
+
* @param [options.duration] The duration of the movie to play in seconds
|
|
2872
2889
|
*
|
|
2873
2890
|
* @return Fulfilled when the movie is done playing, never fails
|
|
2874
2891
|
*/
|
|
@@ -2886,8 +2903,8 @@ var etro = (function () {
|
|
|
2886
2903
|
throw new Error('Already playing');
|
|
2887
2904
|
}
|
|
2888
2905
|
this._paused = this._ended = false;
|
|
2889
|
-
this.
|
|
2890
|
-
this.
|
|
2906
|
+
this._lastRealTime = performance.now();
|
|
2907
|
+
this._endTime = options.duration ? this.currentTime + options.duration : this.duration;
|
|
2891
2908
|
(_a = options.onStart) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
2892
2909
|
// For backwards compatibility
|
|
2893
2910
|
publish(this, 'movie.play', {});
|
|
@@ -2895,15 +2912,19 @@ var etro = (function () {
|
|
|
2895
2912
|
return [4 /*yield*/, new Promise(function (resolve) {
|
|
2896
2913
|
if (!_this.renderingFrame) {
|
|
2897
2914
|
// Not rendering (and not playing), so play.
|
|
2898
|
-
_this._render(
|
|
2915
|
+
_this._render(undefined, resolve);
|
|
2899
2916
|
}
|
|
2900
2917
|
// Stop rendering frame if currently doing so, because playing has higher
|
|
2901
2918
|
// priority. This will affect the next _render call.
|
|
2902
2919
|
_this._renderingFrame = false;
|
|
2903
|
-
})
|
|
2920
|
+
})
|
|
2921
|
+
// After we're done playing, clear the last timestamp
|
|
2922
|
+
];
|
|
2904
2923
|
case 2:
|
|
2905
2924
|
// Repeatedly render frames until the movie ends
|
|
2906
2925
|
_b.sent();
|
|
2926
|
+
// After we're done playing, clear the last timestamp
|
|
2927
|
+
this._lastRealTime = undefined;
|
|
2907
2928
|
return [2 /*return*/];
|
|
2908
2929
|
}
|
|
2909
2930
|
});
|
|
@@ -2977,16 +2998,17 @@ var etro = (function () {
|
|
|
2977
2998
|
// Create the stream
|
|
2978
2999
|
this._currentStream = new MediaStream(tracks);
|
|
2979
3000
|
// Play the movie
|
|
2980
|
-
this._endTime = options.duration ? this.currentTime + options.duration : this.duration;
|
|
2981
3001
|
return [4 /*yield*/, this.play({
|
|
2982
3002
|
onStart: function () {
|
|
2983
3003
|
// Call the user's onStart callback
|
|
2984
3004
|
options.onStart(_this._currentStream);
|
|
2985
|
-
}
|
|
3005
|
+
},
|
|
3006
|
+
duration: options.duration
|
|
2986
3007
|
})
|
|
2987
3008
|
// Clear the stream after the movie is done playing
|
|
2988
3009
|
];
|
|
2989
3010
|
case 2:
|
|
3011
|
+
// Play the movie
|
|
2990
3012
|
_a.sent();
|
|
2991
3013
|
// Clear the stream after the movie is done playing
|
|
2992
3014
|
this._currentStream.getTracks().forEach(function (track) {
|
|
@@ -3121,11 +3143,13 @@ var etro = (function () {
|
|
|
3121
3143
|
return this;
|
|
3122
3144
|
};
|
|
3123
3145
|
/**
|
|
3146
|
+
* Processes one frame of the movie and draws it to the canvas
|
|
3147
|
+
*
|
|
3124
3148
|
* @param [timestamp=performance.now()]
|
|
3125
3149
|
* @param [done=undefined] - Called when done playing or when the current
|
|
3126
3150
|
* frame is loaded
|
|
3127
3151
|
*/
|
|
3128
|
-
Movie.prototype._render = function (
|
|
3152
|
+
Movie.prototype._render = function (timestamp, done) {
|
|
3129
3153
|
var _this = this;
|
|
3130
3154
|
if (timestamp === void 0) { timestamp = performance.now(); }
|
|
3131
3155
|
if (done === void 0) { done = undefined; }
|
|
@@ -3162,8 +3186,6 @@ var etro = (function () {
|
|
|
3162
3186
|
// value and publish a 'imeupdate' event.
|
|
3163
3187
|
this._currentTime = 0;
|
|
3164
3188
|
publish(this, 'movie.timeupdate', { movie: this });
|
|
3165
|
-
this._lastPlayed = performance.now();
|
|
3166
|
-
this._lastPlayedOffset = 0; // this.currentTime
|
|
3167
3189
|
this._renderingFrame = false;
|
|
3168
3190
|
// Stop playback or recording if done (except if it's playing and repeat
|
|
3169
3191
|
// is true)
|
|
@@ -3215,18 +3237,19 @@ var etro = (function () {
|
|
|
3215
3237
|
}
|
|
3216
3238
|
// TODO: Is making a new arrow function every frame bad for performance?
|
|
3217
3239
|
window.requestAnimationFrame(function () {
|
|
3218
|
-
_this._render(
|
|
3240
|
+
_this._render(undefined, done);
|
|
3219
3241
|
});
|
|
3220
3242
|
};
|
|
3221
3243
|
Movie.prototype._updateCurrentTime = function (timestampMs, end) {
|
|
3222
3244
|
// If we're only frame-rendering (current frame only), it doesn't matter if
|
|
3223
3245
|
// it's paused or not.
|
|
3224
3246
|
if (!this._renderingFrame) {
|
|
3225
|
-
var
|
|
3226
|
-
var
|
|
3227
|
-
|
|
3247
|
+
var timestamp = timestampMs / 1000;
|
|
3248
|
+
var delta = timestamp - this._lastRealTime;
|
|
3249
|
+
this._lastRealTime = timestamp;
|
|
3250
|
+
if (delta > 0) {
|
|
3228
3251
|
// Update the current time (don't use setter)
|
|
3229
|
-
this._currentTime
|
|
3252
|
+
this._currentTime += delta;
|
|
3230
3253
|
// For backwards compatibility, publish a 'movie.timeupdate' event.
|
|
3231
3254
|
publish(this, 'movie.timeupdate', { movie: this });
|
|
3232
3255
|
}
|
|
@@ -3235,6 +3258,11 @@ var etro = (function () {
|
|
|
3235
3258
|
}
|
|
3236
3259
|
}
|
|
3237
3260
|
};
|
|
3261
|
+
/**
|
|
3262
|
+
* Draws the movie's background to the canvas
|
|
3263
|
+
*
|
|
3264
|
+
* @param timestamp The current high-resolution timestamp in milliseconds
|
|
3265
|
+
*/
|
|
3238
3266
|
Movie.prototype._renderBackground = function (timestamp) {
|
|
3239
3267
|
this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
3240
3268
|
// Evaluate background color (since it's a dynamic property)
|
|
@@ -3245,7 +3273,7 @@ var etro = (function () {
|
|
|
3245
3273
|
}
|
|
3246
3274
|
};
|
|
3247
3275
|
/**
|
|
3248
|
-
*
|
|
3276
|
+
* Ticks all layers and renders them to the canvas
|
|
3249
3277
|
*/
|
|
3250
3278
|
Movie.prototype._renderLayers = function () {
|
|
3251
3279
|
for (var i = 0; i < this.layers.length; i++) {
|
|
@@ -3290,6 +3318,12 @@ var etro = (function () {
|
|
|
3290
3318
|
}
|
|
3291
3319
|
}
|
|
3292
3320
|
};
|
|
3321
|
+
/**
|
|
3322
|
+
* Applies all of the movie's effects to the canvas
|
|
3323
|
+
*
|
|
3324
|
+
* Note: This method only applies the movie's effects, not the layers'
|
|
3325
|
+
* effects.
|
|
3326
|
+
*/
|
|
3293
3327
|
Movie.prototype._applyEffects = function () {
|
|
3294
3328
|
for (var i = 0; i < this.effects.length; i++) {
|
|
3295
3329
|
var effect = this.effects[i];
|
|
@@ -3316,7 +3350,7 @@ var etro = (function () {
|
|
|
3316
3350
|
}
|
|
3317
3351
|
return new Promise(function (resolve) {
|
|
3318
3352
|
_this._renderingFrame = true;
|
|
3319
|
-
_this._render(
|
|
3353
|
+
_this._render(undefined, resolve);
|
|
3320
3354
|
});
|
|
3321
3355
|
};
|
|
3322
3356
|
/**
|
|
@@ -3365,7 +3399,7 @@ var etro = (function () {
|
|
|
3365
3399
|
*
|
|
3366
3400
|
* Calculated from the end time of the last layer
|
|
3367
3401
|
*/
|
|
3368
|
-
// TODO:
|
|
3402
|
+
// TODO: cache
|
|
3369
3403
|
get: function () {
|
|
3370
3404
|
return this.layers.reduce(function (end, layer) { return Math.max(layer.startTime + layer.duration, end); }, 0);
|
|
3371
3405
|
},
|
|
@@ -3374,6 +3408,7 @@ var etro = (function () {
|
|
|
3374
3408
|
});
|
|
3375
3409
|
/**
|
|
3376
3410
|
* Convenience method for `layers.push()`
|
|
3411
|
+
*
|
|
3377
3412
|
* @param layer
|
|
3378
3413
|
* @return The movie
|
|
3379
3414
|
*/
|
|
@@ -3383,6 +3418,7 @@ var etro = (function () {
|
|
|
3383
3418
|
};
|
|
3384
3419
|
/**
|
|
3385
3420
|
* Convenience method for `effects.push()`
|
|
3421
|
+
*
|
|
3386
3422
|
* @param effect
|
|
3387
3423
|
* @return the movie
|
|
3388
3424
|
*/
|
package/dist/layer/text.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Dynamic, Color } from '../util';
|
|
2
2
|
import { Visual, VisualOptions } from './visual';
|
|
3
|
+
declare enum TextStrokePosition {
|
|
4
|
+
Inside = 0,
|
|
5
|
+
Center = 1,
|
|
6
|
+
Outside = 2
|
|
7
|
+
}
|
|
3
8
|
interface TextOptions extends VisualOptions {
|
|
4
9
|
text: Dynamic<string>;
|
|
5
10
|
font?: Dynamic<string>;
|
|
@@ -23,6 +28,11 @@ interface TextOptions extends VisualOptions {
|
|
|
23
28
|
* @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
|
|
24
29
|
*/
|
|
25
30
|
textDirection?: Dynamic<string>;
|
|
31
|
+
textStroke?: Dynamic<{
|
|
32
|
+
color: Color;
|
|
33
|
+
position?: TextStrokePosition;
|
|
34
|
+
thickness?: number;
|
|
35
|
+
}>;
|
|
26
36
|
}
|
|
27
37
|
declare class Text extends Visual {
|
|
28
38
|
text: Dynamic<string>;
|
|
@@ -47,6 +57,11 @@ declare class Text extends Visual {
|
|
|
47
57
|
* @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
|
|
48
58
|
*/
|
|
49
59
|
textDirection: Dynamic<string>;
|
|
60
|
+
textStroke?: Dynamic<{
|
|
61
|
+
color: Color;
|
|
62
|
+
position?: TextStrokePosition;
|
|
63
|
+
thickness?: number;
|
|
64
|
+
}>;
|
|
50
65
|
private _prevText;
|
|
51
66
|
private _prevFont;
|
|
52
67
|
private _prevMaxWidth;
|
|
@@ -60,4 +75,4 @@ declare class Text extends Visual {
|
|
|
60
75
|
*/
|
|
61
76
|
getDefaultOptions(): TextOptions;
|
|
62
77
|
}
|
|
63
|
-
export { Text, TextOptions };
|
|
78
|
+
export { Text, TextOptions, TextStrokePosition };
|
package/dist/movie/movie.d.ts
CHANGED
|
@@ -62,8 +62,8 @@ export declare class Movie {
|
|
|
62
62
|
private _recording;
|
|
63
63
|
private _currentStream;
|
|
64
64
|
private _endTime;
|
|
65
|
-
|
|
66
|
-
private
|
|
65
|
+
/** The timestamp last frame in seconds */
|
|
66
|
+
private _lastRealTime;
|
|
67
67
|
/**
|
|
68
68
|
* Creates a new movie.
|
|
69
69
|
*/
|
|
@@ -74,11 +74,13 @@ export declare class Movie {
|
|
|
74
74
|
*
|
|
75
75
|
* @param [options]
|
|
76
76
|
* @param [options.onStart] Called when the movie starts playing
|
|
77
|
+
* @param [options.duration] The duration of the movie to play in seconds
|
|
77
78
|
*
|
|
78
79
|
* @return Fulfilled when the movie is done playing, never fails
|
|
79
80
|
*/
|
|
80
81
|
play(options?: {
|
|
81
82
|
onStart?: () => void;
|
|
83
|
+
duration?: number;
|
|
82
84
|
}): Promise<void>;
|
|
83
85
|
/**
|
|
84
86
|
* Updates the rendering canvas and audio destination to the visible canvas
|
|
@@ -136,17 +138,30 @@ export declare class Movie {
|
|
|
136
138
|
*/
|
|
137
139
|
stop(): Movie;
|
|
138
140
|
/**
|
|
141
|
+
* Processes one frame of the movie and draws it to the canvas
|
|
142
|
+
*
|
|
139
143
|
* @param [timestamp=performance.now()]
|
|
140
144
|
* @param [done=undefined] - Called when done playing or when the current
|
|
141
145
|
* frame is loaded
|
|
142
146
|
*/
|
|
143
147
|
private _render;
|
|
144
148
|
private _updateCurrentTime;
|
|
149
|
+
/**
|
|
150
|
+
* Draws the movie's background to the canvas
|
|
151
|
+
*
|
|
152
|
+
* @param timestamp The current high-resolution timestamp in milliseconds
|
|
153
|
+
*/
|
|
145
154
|
private _renderBackground;
|
|
146
155
|
/**
|
|
147
|
-
*
|
|
156
|
+
* Ticks all layers and renders them to the canvas
|
|
148
157
|
*/
|
|
149
158
|
private _renderLayers;
|
|
159
|
+
/**
|
|
160
|
+
* Applies all of the movie's effects to the canvas
|
|
161
|
+
*
|
|
162
|
+
* Note: This method only applies the movie's effects, not the layers'
|
|
163
|
+
* effects.
|
|
164
|
+
*/
|
|
150
165
|
private _applyEffects;
|
|
151
166
|
/**
|
|
152
167
|
* Refreshes the screen
|
|
@@ -180,12 +195,14 @@ export declare class Movie {
|
|
|
180
195
|
get duration(): number;
|
|
181
196
|
/**
|
|
182
197
|
* Convenience method for `layers.push()`
|
|
198
|
+
*
|
|
183
199
|
* @param layer
|
|
184
200
|
* @return The movie
|
|
185
201
|
*/
|
|
186
202
|
addLayer(layer: BaseLayer): Movie;
|
|
187
203
|
/**
|
|
188
204
|
* Convenience method for `effects.push()`
|
|
205
|
+
*
|
|
189
206
|
* @param effect
|
|
190
207
|
* @return the movie
|
|
191
208
|
*/
|
package/karma.conf.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
process.env.CHROME_BIN = require('puppeteer').executablePath()
|
|
5
5
|
|
|
6
|
+
// Make sure TEST_SUITE is set
|
|
7
|
+
if (!process.env.TEST_SUITE) {
|
|
8
|
+
console.error('TEST_SUITE environment variable must be set')
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
module.exports = function (config) {
|
|
7
13
|
config.set({
|
|
8
14
|
|
|
@@ -16,8 +22,8 @@ module.exports = function (config) {
|
|
|
16
22
|
// list of files / patterns to load in the browser
|
|
17
23
|
files: [
|
|
18
24
|
'src/**/*.ts',
|
|
19
|
-
|
|
20
|
-
{ pattern: 'spec/
|
|
25
|
+
`spec/${process.env.TEST_SUITE}/**/*.ts`,
|
|
26
|
+
{ pattern: 'spec/assets/**/*', included: false }
|
|
21
27
|
],
|
|
22
28
|
|
|
23
29
|
// list of files / patterns to exclude
|
|
@@ -57,7 +63,8 @@ module.exports = function (config) {
|
|
|
57
63
|
base: 'Firefox',
|
|
58
64
|
flags: ['-headless'],
|
|
59
65
|
prefs: {
|
|
60
|
-
'network.proxy.type': 0
|
|
66
|
+
'network.proxy.type': 0,
|
|
67
|
+
'media.autoplay.default': 0 // Allow all autoplay
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "etro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "An extendable video-editing framework for the browser",
|
|
5
5
|
"browser": "dist/etro-cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
"test": "spec"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
+
"@commitlint/cli": "^17.6.6",
|
|
14
|
+
"@commitlint/format": "^17.4.4",
|
|
13
15
|
"@rollup/plugin-eslint": "^8.0.2",
|
|
14
16
|
"@types/jest": "^29.0.0",
|
|
15
17
|
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
|
16
18
|
"@typescript-eslint/parser": "^5.30.7",
|
|
17
19
|
"canvas": "^2.11.2",
|
|
20
|
+
"cross-env": "^7.0.3",
|
|
18
21
|
"docdash": "^1.1.1",
|
|
19
22
|
"ecstatic": ">=4.1.3",
|
|
20
23
|
"eslint": "^8.20.0",
|
|
@@ -25,7 +28,9 @@
|
|
|
25
28
|
"eslint-plugin-promise": "^6.0.0",
|
|
26
29
|
"eslint-plugin-standard": "^5.0.0",
|
|
27
30
|
"ev": "0.0.7",
|
|
31
|
+
"gitmoji-cli": "^9.0.0",
|
|
28
32
|
"http-server": "^14.1.1",
|
|
33
|
+
"husky": "^8.0.3",
|
|
29
34
|
"jasmine": "^3.4.0",
|
|
30
35
|
"jasmine-ts": "^0.4.0",
|
|
31
36
|
"karma": "^6.1.1",
|
|
@@ -48,14 +53,17 @@
|
|
|
48
53
|
"scripts": {
|
|
49
54
|
"build": "rollup -c",
|
|
50
55
|
"doc": "rm -rf docs && npx typedoc src/etro.ts --excludePrivate --readme none",
|
|
51
|
-
"
|
|
56
|
+
"prepare": "husky install",
|
|
52
57
|
"effects": "node scripts/effect/save-effect-samples.js",
|
|
53
58
|
"lint": "npm run --silent lint:main && npm run --silent lint:test && npm run --silent lint:examples",
|
|
54
|
-
"lint:main
|
|
55
|
-
"lint:
|
|
56
|
-
"lint:
|
|
59
|
+
"fix": "npm run --silent lint:main -- --fix && npm run --silent lint:test -- --fix && npm run --silent lint:examples -- --fix",
|
|
60
|
+
"lint:main": "eslint -c eslint.typescript-conf.js --ext .ts src",
|
|
61
|
+
"lint:test": "eslint -c eslint.test-conf.js --ext .ts spec",
|
|
62
|
+
"lint:examples": "eslint -c eslint.example-conf.js --ext .html examples",
|
|
57
63
|
"start": "http-server",
|
|
58
|
-
"test": "karma start",
|
|
64
|
+
"test:unit": "cross-env TEST_SUITE=unit karma start",
|
|
65
|
+
"test:smoke": "cross-env TEST_SUITE=smoke karma start",
|
|
66
|
+
"test:integration": "cross-env TEST_SUITE=integration karma start",
|
|
59
67
|
"release": "shipjs prepare"
|
|
60
68
|
},
|
|
61
69
|
"repository": {
|
|
@@ -39,7 +39,7 @@ function createDirs(filePath) {
|
|
|
39
39
|
// remove prefix and save to png
|
|
40
40
|
const buffer = Buffer.from(item.data.replace(/^data:image\/png;base64,/, ''), 'base64')
|
|
41
41
|
console.log(`writing ${item.path} ...`)
|
|
42
|
-
const path = projectDir + '/spec/
|
|
42
|
+
const path = projectDir + '/spec/assets/effect/' + item.path
|
|
43
43
|
createDirs(path)
|
|
44
44
|
fs.writeFileSync(path, buffer)
|
|
45
45
|
})
|
package/ship.config.js
CHANGED
|
@@ -24,7 +24,18 @@ module.exports = {
|
|
|
24
24
|
release.setDate(new Date()) // today
|
|
25
25
|
const newChangelog = parsed.toString()
|
|
26
26
|
fs.writeFileSync(changelogFile, newChangelog, 'utf8')
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
|
+
releases: [
|
|
29
|
+
{
|
|
30
|
+
extractChangelog: ({ version, dir }) => {
|
|
31
|
+
const changelogFile = `${dir}/CHANGELOG.md`
|
|
32
|
+
const changelog = fs.readFileSync(changelogFile, 'utf8')
|
|
33
|
+
const parsed = parser(changelog)
|
|
34
|
+
const release = parsed.findRelease(version)
|
|
35
|
+
return `${release.toString()}\n${release.getCompareLink()}`
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
]
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
class Changelog {
|
|
@@ -50,7 +50,7 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
|
|
|
50
50
|
private _unstretchedDuration: number
|
|
51
51
|
private _playbackRate: number
|
|
52
52
|
private _initialized: boolean
|
|
53
|
-
private
|
|
53
|
+
private _lastAudioDestination: AudioNode
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* @param options
|
|
@@ -85,9 +85,12 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
|
|
|
85
85
|
applyOptions(options, this)
|
|
86
86
|
|
|
87
87
|
const load = () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
if (options.duration < 0) {
|
|
89
|
+
throw new Error('Invalid options.duration. It must be a non-negative value.')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.sourceStartTime > this.source.duration) {
|
|
93
|
+
throw new Error('options.sourceStartTime cannot exceed options.source.duration')
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
this._unstretchedDuration = options.duration || (this.source.duration - this.sourceStartTime)
|
|
@@ -123,36 +126,18 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
|
|
|
123
126
|
|
|
124
127
|
// TODO: on unattach?
|
|
125
128
|
subscribe(movie, 'audiodestinationupdate', event => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.audioNode.connect(event.destination)
|
|
131
|
-
}
|
|
129
|
+
this.audioNode.disconnect(this._lastAudioDestination)
|
|
130
|
+
this.audioNode.connect(event.destination)
|
|
131
|
+
|
|
132
|
+
this._lastAudioDestination = event.destination
|
|
132
133
|
})
|
|
133
134
|
|
|
134
135
|
// connect to audiocontext
|
|
135
136
|
this._audioNode = this.audioNode || movie.actx.createMediaElementSource(this.source)
|
|
136
137
|
|
|
137
|
-
// Spy on connect and disconnect to remember if it connected to
|
|
138
|
-
// actx.destination (for Movie#record).
|
|
139
|
-
const oldConnect = this._audioNode.connect.bind(this.audioNode)
|
|
140
|
-
this._audioNode.connect = <T extends AudioDestinationNode>(destination: T, outputIndex?: number, inputIndex?: number): AudioNode => {
|
|
141
|
-
this._connectedToDestination = destination === movie.actx.destination
|
|
142
|
-
return oldConnect(destination, outputIndex, inputIndex)
|
|
143
|
-
}
|
|
144
|
-
const oldDisconnect = this._audioNode.disconnect.bind(this.audioNode)
|
|
145
|
-
this._audioNode.disconnect = <T extends AudioDestinationNode>(destination?: T | number, output?: number, input?: number): AudioNode => {
|
|
146
|
-
if (this._connectedToDestination &&
|
|
147
|
-
destination === movie.actx.destination) {
|
|
148
|
-
this._connectedToDestination = false
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return oldDisconnect(destination, output, input)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
138
|
// Connect to actx.destination by default (can be rewired by user)
|
|
155
139
|
this.audioNode.connect(movie.actx.destination)
|
|
140
|
+
this._lastAudioDestination = movie.actx.destination
|
|
156
141
|
}
|
|
157
142
|
|
|
158
143
|
detach () {
|
|
@@ -170,7 +155,11 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
|
|
|
170
155
|
seek (time: number): void {
|
|
171
156
|
super.seek(time)
|
|
172
157
|
|
|
173
|
-
|
|
158
|
+
if (isNaN(this.currentTime)) {
|
|
159
|
+
this.source.currentTime = this.sourceStartTime
|
|
160
|
+
} else {
|
|
161
|
+
this.source.currentTime = this.currentTime + this.sourceStartTime
|
|
162
|
+
}
|
|
174
163
|
}
|
|
175
164
|
|
|
176
165
|
render () {
|