etro 0.9.0 → 0.10.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/CHANGELOG.md +50 -0
- package/CONTRIBUTING.md +25 -34
- package/README.md +9 -17
- package/dist/custom-array.d.ts +10 -0
- package/dist/effect/base.d.ts +10 -1
- package/dist/effect/shader.d.ts +11 -1
- package/dist/effect/stack.d.ts +6 -2
- package/dist/etro-cjs.js +1182 -592
- package/dist/etro-iife.js +1182 -592
- package/dist/event.d.ts +10 -5
- package/dist/layer/audio-source.d.ts +9 -4
- package/dist/layer/audio.d.ts +15 -2
- package/dist/layer/base.d.ts +49 -3
- package/dist/layer/image.d.ts +15 -1
- package/dist/layer/text.d.ts +6 -3
- package/dist/layer/video.d.ts +13 -1
- package/dist/layer/visual-source.d.ts +18 -3
- package/dist/layer/visual.d.ts +11 -7
- package/dist/movie/effects.d.ts +6 -0
- package/dist/movie/index.d.ts +1 -0
- package/dist/movie/layers.d.ts +6 -0
- package/dist/movie/movie.d.ts +260 -0
- package/dist/object.d.ts +9 -2
- package/dist/util.d.ts +4 -10
- package/eslint.conf.js +4 -2
- package/eslint.test-conf.js +1 -2
- package/karma.conf.js +10 -14
- package/package.json +23 -22
- package/scripts/{gen-effect-samples.html → effect/gen-effect-samples.html} +24 -0
- package/scripts/{save-effect-samples.js → effect/save-effect-samples.js} +1 -1
- package/src/custom-array.ts +43 -0
- package/src/effect/base.ts +23 -22
- package/src/effect/gaussian-blur.ts +11 -6
- package/src/effect/pixelate.ts +3 -3
- package/src/effect/shader.ts +33 -27
- package/src/effect/stack.ts +43 -30
- package/src/effect/transform.ts +16 -9
- package/src/event.ts +111 -21
- package/src/layer/audio-source.ts +60 -20
- package/src/layer/audio.ts +25 -3
- package/src/layer/base.ts +79 -25
- package/src/layer/image.ts +26 -2
- package/src/layer/text.ts +11 -4
- package/src/layer/video.ts +31 -4
- package/src/layer/visual-source.ts +70 -8
- package/src/layer/visual.ts +57 -35
- package/src/movie/effects.ts +26 -0
- package/src/movie/index.ts +1 -0
- package/src/movie/layers.ts +26 -0
- package/src/movie/movie.ts +855 -0
- package/src/object.ts +9 -2
- package/src/util.ts +68 -89
- package/tsconfig.json +3 -1
- package/dist/movie.d.ts +0 -201
- package/examples/application/readme-screenshot.html +0 -85
- package/examples/application/video-player.html +0 -130
- package/examples/application/webcam.html +0 -28
- package/examples/introduction/audio.html +0 -64
- package/examples/introduction/effects.html +0 -79
- package/examples/introduction/export.html +0 -83
- package/examples/introduction/functions.html +0 -37
- package/examples/introduction/hello-world1.html +0 -37
- package/examples/introduction/hello-world2.html +0 -32
- package/examples/introduction/keyframes.html +0 -79
- package/examples/introduction/media.html +0 -63
- package/examples/introduction/text.html +0 -31
- package/private-todo.txt +0 -70
- package/src/movie.ts +0 -742
package/dist/etro-cjs.js
CHANGED
|
@@ -38,11 +38,68 @@ var __assign = function() {
|
|
|
38
38
|
return t;
|
|
39
39
|
};
|
|
40
40
|
return __assign.apply(this, arguments);
|
|
41
|
-
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
44
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
45
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
46
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
47
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
48
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
49
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function __generator(thisArg, body) {
|
|
54
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
55
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
56
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
57
|
+
function step(op) {
|
|
58
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
59
|
+
while (_) try {
|
|
60
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
61
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
62
|
+
switch (op[0]) {
|
|
63
|
+
case 0: case 1: t = op; break;
|
|
64
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
65
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
66
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
67
|
+
default:
|
|
68
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
69
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
70
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
71
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
72
|
+
if (t[2]) _.ops.pop();
|
|
73
|
+
_.trys.pop(); continue;
|
|
74
|
+
}
|
|
75
|
+
op = body.call(thisArg, _);
|
|
76
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
77
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
42
80
|
|
|
43
81
|
/**
|
|
44
82
|
* @module event
|
|
45
83
|
*/
|
|
84
|
+
var DeprecatedEvent = /** @class */ (function () {
|
|
85
|
+
function DeprecatedEvent(replacement, message) {
|
|
86
|
+
if (message === void 0) { message = undefined; }
|
|
87
|
+
this.replacement = replacement;
|
|
88
|
+
this.message = message;
|
|
89
|
+
}
|
|
90
|
+
DeprecatedEvent.prototype.toString = function () {
|
|
91
|
+
var str = '';
|
|
92
|
+
if (this.replacement) {
|
|
93
|
+
str += "Use ".concat(this.replacement, " instead.");
|
|
94
|
+
}
|
|
95
|
+
if (this.message) {
|
|
96
|
+
str += " ".concat(this.message);
|
|
97
|
+
}
|
|
98
|
+
return str;
|
|
99
|
+
};
|
|
100
|
+
return DeprecatedEvent;
|
|
101
|
+
}());
|
|
102
|
+
var deprecatedEvents = {};
|
|
46
103
|
/**
|
|
47
104
|
* An event type
|
|
48
105
|
* @private
|
|
@@ -52,11 +109,14 @@ var TypeId = /** @class */ (function () {
|
|
|
52
109
|
this._parts = id.split('.');
|
|
53
110
|
}
|
|
54
111
|
TypeId.prototype.contains = function (other) {
|
|
55
|
-
if (other._parts.length > this._parts.length)
|
|
112
|
+
if (other._parts.length > this._parts.length) {
|
|
56
113
|
return false;
|
|
57
|
-
|
|
58
|
-
|
|
114
|
+
}
|
|
115
|
+
for (var i = 0; i < other._parts.length; i++) {
|
|
116
|
+
if (other._parts[i] !== this._parts[i]) {
|
|
59
117
|
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
60
120
|
return true;
|
|
61
121
|
};
|
|
62
122
|
TypeId.prototype.toString = function () {
|
|
@@ -64,23 +124,50 @@ var TypeId = /** @class */ (function () {
|
|
|
64
124
|
};
|
|
65
125
|
return TypeId;
|
|
66
126
|
}());
|
|
127
|
+
function deprecate(type, newType, message) {
|
|
128
|
+
if (message === void 0) { message = undefined; }
|
|
129
|
+
deprecatedEvents[type] = new DeprecatedEvent(newType, message);
|
|
130
|
+
}
|
|
131
|
+
function subscribeOnce(target, type, listener) {
|
|
132
|
+
var wrapped = function (event) {
|
|
133
|
+
unsubscribe(target, wrapped);
|
|
134
|
+
listener(event);
|
|
135
|
+
};
|
|
136
|
+
subscribe(target, type, wrapped);
|
|
137
|
+
}
|
|
138
|
+
function subscribeMany(target, type, listener) {
|
|
139
|
+
if (!listeners.has(target)) {
|
|
140
|
+
listeners.set(target, []);
|
|
141
|
+
}
|
|
142
|
+
listeners.get(target).push({ type: new TypeId(type), listener: listener });
|
|
143
|
+
}
|
|
67
144
|
/**
|
|
68
|
-
* Listen for an event or category of events
|
|
145
|
+
* Listen for an event or category of events.
|
|
69
146
|
*
|
|
70
|
-
* @param target -
|
|
147
|
+
* @param target - an etro object
|
|
71
148
|
* @param type - the id of the type (can contain subtypes, such as
|
|
72
149
|
* "type.subtype")
|
|
73
150
|
* @param listener
|
|
151
|
+
* @param options - options
|
|
152
|
+
* @param options.once - if true, the listener will only be called once
|
|
74
153
|
*/
|
|
75
|
-
function subscribe(target, type, listener) {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
154
|
+
function subscribe(target, type, listener, options) {
|
|
155
|
+
if (options === void 0) { options = {}; }
|
|
156
|
+
// Check if this event is deprecated.
|
|
157
|
+
if (Object.keys(deprecatedEvents).includes(type)) {
|
|
158
|
+
console.warn("Event ".concat(type, " is deprecated. ").concat(deprecatedEvents[type]));
|
|
159
|
+
}
|
|
160
|
+
if (options.once) {
|
|
161
|
+
subscribeOnce(target, type, listener);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
subscribeMany(target, type, listener);
|
|
165
|
+
}
|
|
79
166
|
}
|
|
80
167
|
/**
|
|
81
168
|
* Remove an event listener
|
|
82
169
|
*
|
|
83
|
-
* @param target -
|
|
170
|
+
* @param target - an etro object
|
|
84
171
|
* @param type - the id of the type (can contain subtypes, such as
|
|
85
172
|
* "type.subtype")
|
|
86
173
|
* @param listener
|
|
@@ -88,33 +175,36 @@ function subscribe(target, type, listener) {
|
|
|
88
175
|
function unsubscribe(target, listener) {
|
|
89
176
|
// Make sure `listener` has been added with `subscribe`.
|
|
90
177
|
if (!listeners.has(target) ||
|
|
91
|
-
!listeners.get(target).map(function (pair) { return pair.listener; }).includes(listener))
|
|
178
|
+
!listeners.get(target).map(function (pair) { return pair.listener; }).includes(listener)) {
|
|
92
179
|
throw new Error('No matching event listener to remove');
|
|
180
|
+
}
|
|
93
181
|
var removed = listeners.get(target)
|
|
94
182
|
.filter(function (pair) { return pair.listener !== listener; });
|
|
95
183
|
listeners.set(target, removed);
|
|
96
184
|
}
|
|
97
185
|
/**
|
|
98
|
-
*
|
|
186
|
+
* Publish an event to all listeners without checking if it is deprecated.
|
|
99
187
|
*
|
|
100
|
-
* @param target
|
|
101
|
-
* @param type
|
|
102
|
-
*
|
|
103
|
-
* @
|
|
188
|
+
* @param target
|
|
189
|
+
* @param type
|
|
190
|
+
* @param event
|
|
191
|
+
* @returns
|
|
104
192
|
*/
|
|
105
|
-
function
|
|
193
|
+
function _publish(target, type, event) {
|
|
106
194
|
event.target = target; // could be a proxy
|
|
107
195
|
event.type = type;
|
|
108
196
|
var t = new TypeId(type);
|
|
109
|
-
if (!listeners.has(target))
|
|
197
|
+
if (!listeners.has(target)) {
|
|
110
198
|
// No event fired
|
|
111
199
|
return null;
|
|
200
|
+
}
|
|
112
201
|
// Call event listeners for this event.
|
|
113
202
|
var listenersForType = [];
|
|
114
203
|
for (var i = 0; i < listeners.get(target).length; i++) {
|
|
115
204
|
var item = listeners.get(target)[i];
|
|
116
|
-
if (t.contains(item.type))
|
|
205
|
+
if (t.contains(item.type)) {
|
|
117
206
|
listenersForType.push(item.listener);
|
|
207
|
+
}
|
|
118
208
|
}
|
|
119
209
|
for (var i = 0; i < listenersForType.length; i++) {
|
|
120
210
|
var listener = listenersForType[i];
|
|
@@ -122,9 +212,33 @@ function publish(target, type, event) {
|
|
|
122
212
|
}
|
|
123
213
|
return event;
|
|
124
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Emits an event to all listeners
|
|
217
|
+
*
|
|
218
|
+
* @param target - an etro object
|
|
219
|
+
* @param type - the id of the type (can contain subtypes, such as
|
|
220
|
+
* "type.subtype")
|
|
221
|
+
* @param event - any additional event data
|
|
222
|
+
*/
|
|
223
|
+
function publish(target, type, event) {
|
|
224
|
+
// Check if this event is deprecated only if it can be replaced.
|
|
225
|
+
if (Object.keys(deprecatedEvents).includes(type) && deprecatedEvents[type].replacement) {
|
|
226
|
+
throw new Error("Event ".concat(type, " is deprecated. ").concat(deprecatedEvents[type]));
|
|
227
|
+
}
|
|
228
|
+
// Check for deprecated events that this event replaces.
|
|
229
|
+
for (var deprecated in deprecatedEvents) {
|
|
230
|
+
var deprecatedEvent = deprecatedEvents[deprecated];
|
|
231
|
+
if (type === deprecatedEvent.replacement) {
|
|
232
|
+
_publish(target, deprecated, __assign({}, event));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return _publish(target, type, event);
|
|
236
|
+
}
|
|
125
237
|
var listeners = new WeakMap();
|
|
126
238
|
|
|
127
239
|
var event = /*#__PURE__*/Object.freeze({
|
|
240
|
+
__proto__: null,
|
|
241
|
+
deprecate: deprecate,
|
|
128
242
|
subscribe: subscribe,
|
|
129
243
|
unsubscribe: unsubscribe,
|
|
130
244
|
publish: publish
|
|
@@ -142,8 +256,9 @@ var event = /*#__PURE__*/Object.freeze({
|
|
|
142
256
|
function getPropertyDescriptor(obj, name) {
|
|
143
257
|
do {
|
|
144
258
|
var propDesc = Object.getOwnPropertyDescriptor(obj, name);
|
|
145
|
-
if (propDesc)
|
|
259
|
+
if (propDesc) {
|
|
146
260
|
return propDesc;
|
|
261
|
+
}
|
|
147
262
|
obj = Object.getPrototypeOf(obj);
|
|
148
263
|
} while (obj);
|
|
149
264
|
return undefined;
|
|
@@ -152,36 +267,45 @@ function getPropertyDescriptor(obj, name) {
|
|
|
152
267
|
* Merges `options` with `defaultOptions`, and then copies the properties with
|
|
153
268
|
* the keys in `defaultOptions` from the merged object to `destObj`.
|
|
154
269
|
*
|
|
270
|
+
* @deprecated Each option should be copied individually, and the default value
|
|
271
|
+
* should be set in the constructor. See
|
|
272
|
+
* {@link https://github.com/etro-js/etro/issues/131} for more info.
|
|
273
|
+
*
|
|
155
274
|
* @return
|
|
156
275
|
*/
|
|
157
276
|
// TODO: Make methods like getDefaultOptions private
|
|
158
277
|
function applyOptions(options, destObj) {
|
|
159
278
|
var defaultOptions = destObj.getDefaultOptions();
|
|
160
279
|
// Validate; make sure `keys` doesn't have any extraneous items
|
|
161
|
-
for (var option in options)
|
|
280
|
+
for (var option in options) {
|
|
162
281
|
// eslint-disable-next-line no-prototype-builtins
|
|
163
|
-
if (!defaultOptions.hasOwnProperty(option))
|
|
282
|
+
if (!defaultOptions.hasOwnProperty(option)) {
|
|
164
283
|
throw new Error("Invalid option: '" + option + "'");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
165
286
|
// Merge options and defaultOptions
|
|
166
287
|
options = __assign(__assign({}, defaultOptions), options);
|
|
167
288
|
// Copy options
|
|
168
289
|
for (var option in options) {
|
|
169
290
|
var propDesc = getPropertyDescriptor(destObj, option);
|
|
170
291
|
// Update the property as long as the property has not been set (unless if it has a setter)
|
|
171
|
-
if (!propDesc || propDesc.set)
|
|
292
|
+
if (!propDesc || propDesc.set) {
|
|
172
293
|
destObj[option] = options[option];
|
|
294
|
+
}
|
|
173
295
|
}
|
|
174
296
|
}
|
|
175
297
|
// This must be cleared at the start of each frame
|
|
176
298
|
var valCache = new WeakMap();
|
|
177
299
|
function cacheValue(element, path, value) {
|
|
178
300
|
// Initiate movie cache
|
|
179
|
-
if (!valCache.has(element.movie))
|
|
301
|
+
if (!valCache.has(element.movie)) {
|
|
180
302
|
valCache.set(element.movie, new WeakMap());
|
|
303
|
+
}
|
|
181
304
|
var movieCache = valCache.get(element.movie);
|
|
182
|
-
//
|
|
183
|
-
if (!movieCache.has(element))
|
|
305
|
+
// Initiate element cache
|
|
306
|
+
if (!movieCache.has(element)) {
|
|
184
307
|
movieCache.set(element, {});
|
|
308
|
+
}
|
|
185
309
|
var elementCache = movieCache.get(element);
|
|
186
310
|
// Cache the value
|
|
187
311
|
elementCache[path] = value;
|
|
@@ -221,13 +345,16 @@ var KeyFrame = /** @class */ (function () {
|
|
|
221
345
|
return this;
|
|
222
346
|
};
|
|
223
347
|
KeyFrame.prototype.evaluate = function (time) {
|
|
224
|
-
if (this.value.length === 0)
|
|
348
|
+
if (this.value.length === 0) {
|
|
225
349
|
throw new Error('Empty keyframe');
|
|
226
|
-
|
|
350
|
+
}
|
|
351
|
+
if (time === undefined) {
|
|
227
352
|
throw new Error('|time| is undefined or null');
|
|
353
|
+
}
|
|
228
354
|
var firstTime = this.value[0][0];
|
|
229
|
-
if (time < firstTime)
|
|
355
|
+
if (time < firstTime) {
|
|
230
356
|
throw new Error('No keyframe point before |time|');
|
|
357
|
+
}
|
|
231
358
|
// I think reduce are slow to do per-frame (or more)?
|
|
232
359
|
for (var i = 0; i < this.value.length; i++) {
|
|
233
360
|
var startTime = this.value[i][0];
|
|
@@ -236,7 +363,7 @@ var KeyFrame = /** @class */ (function () {
|
|
|
236
363
|
if (i + 1 < this.value.length) {
|
|
237
364
|
var endTime = this.value[i + 1][0];
|
|
238
365
|
var endValue = this.value[i + 1][1];
|
|
239
|
-
if (startTime <= time && time < endTime)
|
|
366
|
+
if (startTime <= time && time < endTime) {
|
|
240
367
|
// No need for endValue if it is flat interpolation
|
|
241
368
|
// TODO: support custom interpolation for 'other' types?
|
|
242
369
|
if (!(typeof startValue === 'number' || typeof endValue === 'object')) {
|
|
@@ -252,6 +379,7 @@ var KeyFrame = /** @class */ (function () {
|
|
|
252
379
|
endValue, // eslint-disable-line @typescript-eslint/ban-types
|
|
253
380
|
percentProgress, this.interpolationKeys);
|
|
254
381
|
}
|
|
382
|
+
}
|
|
255
383
|
}
|
|
256
384
|
else {
|
|
257
385
|
// Repeat last value forever
|
|
@@ -277,23 +405,28 @@ var KeyFrame = /** @class */ (function () {
|
|
|
277
405
|
// TODO: Is this function efficient?
|
|
278
406
|
// TODO: Update doc @params to allow for keyframes
|
|
279
407
|
function val(element, path, time) {
|
|
280
|
-
if (hasCachedValue(element, path))
|
|
408
|
+
if (hasCachedValue(element, path)) {
|
|
281
409
|
return getCachedValue(element, path);
|
|
410
|
+
}
|
|
282
411
|
// Get property of element at path
|
|
283
412
|
var pathParts = path.split('.');
|
|
284
413
|
var property = element[pathParts.shift()];
|
|
285
|
-
while (pathParts.length > 0)
|
|
414
|
+
while (pathParts.length > 0) {
|
|
286
415
|
property = property[pathParts.shift()];
|
|
416
|
+
}
|
|
287
417
|
// Property filter function
|
|
288
418
|
var process = element.propertyFilters[path];
|
|
289
419
|
var value;
|
|
290
|
-
if (property instanceof KeyFrame)
|
|
420
|
+
if (property instanceof KeyFrame) {
|
|
291
421
|
value = property.evaluate(time);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
422
|
+
}
|
|
423
|
+
else if (typeof property === 'function') {
|
|
424
|
+
value = property(element, time);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
295
427
|
// Simple value
|
|
296
428
|
value = property;
|
|
429
|
+
}
|
|
297
430
|
return cacheValue(element, path, process ? process.call(element, value) : value);
|
|
298
431
|
}
|
|
299
432
|
/* export function floorInterp(x1, x2, t, objectKeys) {
|
|
@@ -304,15 +437,18 @@ function val(element, path, time) {
|
|
|
304
437
|
}, Object.create(Object.getPrototypeOf(x1)));
|
|
305
438
|
} */
|
|
306
439
|
function linearInterp(x1, x2, t, objectKeys) {
|
|
307
|
-
if (typeof x1 !== typeof x2)
|
|
440
|
+
if (typeof x1 !== typeof x2) {
|
|
308
441
|
throw new Error('Type mismatch');
|
|
309
|
-
|
|
442
|
+
}
|
|
443
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object') {
|
|
310
444
|
// Flat interpolation (floor)
|
|
311
445
|
return x1;
|
|
446
|
+
}
|
|
312
447
|
if (typeof x1 === 'object') { // to work with objects (including arrays)
|
|
313
448
|
// TODO: make this code DRY
|
|
314
|
-
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
|
|
449
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
|
|
315
450
|
throw new Error('Prototype mismatch');
|
|
451
|
+
}
|
|
316
452
|
// Preserve prototype of objects
|
|
317
453
|
var int = Object.create(Object.getPrototypeOf(x1));
|
|
318
454
|
// Take the intersection of properties
|
|
@@ -320,8 +456,9 @@ function linearInterp(x1, x2, t, objectKeys) {
|
|
|
320
456
|
for (var i = 0; i < keys.length; i++) {
|
|
321
457
|
var key = keys[i];
|
|
322
458
|
// eslint-disable-next-line no-prototype-builtins
|
|
323
|
-
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
|
|
459
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
|
|
324
460
|
continue;
|
|
461
|
+
}
|
|
325
462
|
int[key] = linearInterp(x1[key], x2[key], t);
|
|
326
463
|
}
|
|
327
464
|
return int;
|
|
@@ -329,14 +466,17 @@ function linearInterp(x1, x2, t, objectKeys) {
|
|
|
329
466
|
return (1 - t) * x1 + t * x2;
|
|
330
467
|
}
|
|
331
468
|
function cosineInterp(x1, x2, t, objectKeys) {
|
|
332
|
-
if (typeof x1 !== typeof x2)
|
|
469
|
+
if (typeof x1 !== typeof x2) {
|
|
333
470
|
throw new Error('Type mismatch');
|
|
334
|
-
|
|
471
|
+
}
|
|
472
|
+
if (typeof x1 !== 'number' && typeof x1 !== 'object') {
|
|
335
473
|
// Flat interpolation (floor)
|
|
336
474
|
return x1;
|
|
475
|
+
}
|
|
337
476
|
if (typeof x1 === 'object' && typeof x2 === 'object') { // to work with objects (including arrays)
|
|
338
|
-
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2))
|
|
477
|
+
if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
|
|
339
478
|
throw new Error('Prototype mismatch');
|
|
479
|
+
}
|
|
340
480
|
// Preserve prototype of objects
|
|
341
481
|
var int = Object.create(Object.getPrototypeOf(x1));
|
|
342
482
|
// Take the intersection of properties
|
|
@@ -344,8 +484,9 @@ function cosineInterp(x1, x2, t, objectKeys) {
|
|
|
344
484
|
for (var i = 0; i < keys.length; i++) {
|
|
345
485
|
var key = keys[i];
|
|
346
486
|
// eslint-disable-next-line no-prototype-builtins
|
|
347
|
-
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key))
|
|
487
|
+
if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
|
|
348
488
|
continue;
|
|
489
|
+
}
|
|
349
490
|
int[key] = cosineInterp(x1[key], x2[key], t);
|
|
350
491
|
}
|
|
351
492
|
return int;
|
|
@@ -374,7 +515,7 @@ var Color = /** @class */ (function () {
|
|
|
374
515
|
* Converts to a CSS color
|
|
375
516
|
*/
|
|
376
517
|
Color.prototype.toString = function () {
|
|
377
|
-
return "rgba("
|
|
518
|
+
return "rgba(".concat(this.r, ", ").concat(this.g, ", ").concat(this.b, ", ").concat(this.a, ")");
|
|
378
519
|
};
|
|
379
520
|
return Color;
|
|
380
521
|
}());
|
|
@@ -425,17 +566,22 @@ var Font = /** @class */ (function () {
|
|
|
425
566
|
*/
|
|
426
567
|
Font.prototype.toString = function () {
|
|
427
568
|
var s = '';
|
|
428
|
-
if (this.style !== 'normal')
|
|
569
|
+
if (this.style !== 'normal') {
|
|
429
570
|
s += this.style + ' ';
|
|
430
|
-
|
|
571
|
+
}
|
|
572
|
+
if (this.variant !== 'normal') {
|
|
431
573
|
s += this.variant + ' ';
|
|
432
|
-
|
|
574
|
+
}
|
|
575
|
+
if (this.weight !== 'normal') {
|
|
433
576
|
s += this.weight + ' ';
|
|
434
|
-
|
|
577
|
+
}
|
|
578
|
+
if (this.stretch !== 'normal') {
|
|
435
579
|
s += this.stretch + ' ';
|
|
436
|
-
|
|
437
|
-
|
|
580
|
+
}
|
|
581
|
+
s += "".concat(this.size).concat(this.sizeUnit, " ");
|
|
582
|
+
if (this.lineHeight !== 'normal') {
|
|
438
583
|
s += this.lineHeight + ' ';
|
|
584
|
+
}
|
|
439
585
|
s += this.family;
|
|
440
586
|
return s;
|
|
441
587
|
};
|
|
@@ -450,7 +596,7 @@ var parseFontEl = document.createElement('div');
|
|
|
450
596
|
*/
|
|
451
597
|
function parseFont(str) {
|
|
452
598
|
// Assign css string to html element
|
|
453
|
-
parseFontEl.setAttribute('style', "font: "
|
|
599
|
+
parseFontEl.setAttribute('style', "font: ".concat(str));
|
|
454
600
|
var _a = parseFontEl.style, fontSize = _a.fontSize, fontFamily = _a.fontFamily, fontStyle = _a.fontStyle, fontVariant = _a.fontVariant, fontWeight = _a.fontWeight, lineHeight = _a.lineHeight;
|
|
455
601
|
parseFontEl.removeAttribute('style');
|
|
456
602
|
var size = parseFloat(fontSize);
|
|
@@ -475,61 +621,12 @@ function mapPixels(mapper, canvas, ctx, x, y, width, height, flush) {
|
|
|
475
621
|
width = width || canvas.width;
|
|
476
622
|
height = height || canvas.height;
|
|
477
623
|
var frame = ctx.getImageData(x, y, width, height);
|
|
478
|
-
for (var i = 0, l = frame.data.length; i < l; i += 4)
|
|
624
|
+
for (var i = 0, l = frame.data.length; i < l; i += 4) {
|
|
479
625
|
mapper(frame.data, i);
|
|
480
|
-
|
|
626
|
+
}
|
|
627
|
+
if (flush) {
|
|
481
628
|
ctx.putImageData(frame, x, y);
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* <p>Emits "change" event when public properties updated, recursively.
|
|
485
|
-
* <p>Must be called before any watchable properties are set, and only once in
|
|
486
|
-
* the prototype chain.
|
|
487
|
-
*
|
|
488
|
-
* @deprecated Will be removed in the future (see issue #130)
|
|
489
|
-
*
|
|
490
|
-
* @param target - object to watch
|
|
491
|
-
*/
|
|
492
|
-
function watchPublic(target) {
|
|
493
|
-
var getPath = function (receiver, prop) {
|
|
494
|
-
return (receiver === proxy ? '' : (paths.get(receiver) + '.')) + prop;
|
|
495
|
-
};
|
|
496
|
-
var callback = function (prop, val, receiver) {
|
|
497
|
-
// Public API property updated, emit 'modify' event.
|
|
498
|
-
publish(proxy, target.type + ".change.modify", { property: getPath(receiver, prop), newValue: val });
|
|
499
|
-
};
|
|
500
|
-
var canWatch = function (receiver, prop) { return !prop.startsWith('_') &&
|
|
501
|
-
(receiver.publicExcludes === undefined || !receiver.publicExcludes.includes(prop)); };
|
|
502
|
-
// The path to each child property (each is a unique proxy)
|
|
503
|
-
var paths = new WeakMap();
|
|
504
|
-
var handler = {
|
|
505
|
-
set: function (obj, prop, val, receiver) {
|
|
506
|
-
// Recurse
|
|
507
|
-
if (typeof val === 'object' && val !== null && !paths.has(val) && canWatch(receiver, prop)) {
|
|
508
|
-
val = new Proxy(val, handler);
|
|
509
|
-
paths.set(val, getPath(receiver, prop));
|
|
510
|
-
}
|
|
511
|
-
// Set property or attribute
|
|
512
|
-
// Search prototype chain for the closest setter
|
|
513
|
-
var objProto = obj;
|
|
514
|
-
while ((objProto = Object.getPrototypeOf(objProto))) {
|
|
515
|
-
var propDesc = Object.getOwnPropertyDescriptor(objProto, prop);
|
|
516
|
-
if (propDesc && propDesc.set) {
|
|
517
|
-
// Call setter, supplying proxy as this (fixes event bugs)
|
|
518
|
-
propDesc.set.call(receiver, val);
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (!objProto)
|
|
523
|
-
// Couldn't find setter; set value on instance
|
|
524
|
-
obj[prop] = val;
|
|
525
|
-
// Check if the property isn't blacklisted in publicExcludes.
|
|
526
|
-
if (canWatch(receiver, prop))
|
|
527
|
-
callback(prop, val, receiver);
|
|
528
|
-
return true;
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
var proxy = new Proxy(target, handler);
|
|
532
|
-
return proxy;
|
|
629
|
+
}
|
|
533
630
|
}
|
|
534
631
|
|
|
535
632
|
/**
|
|
@@ -555,45 +652,70 @@ function AudioSourceMixin(superclass) {
|
|
|
555
652
|
*/
|
|
556
653
|
function MixedAudioSource(options) {
|
|
557
654
|
var _this = this;
|
|
655
|
+
var _a;
|
|
656
|
+
if (!options.source) {
|
|
657
|
+
throw new Error('Property "source" is required in options');
|
|
658
|
+
}
|
|
558
659
|
var onload = options.onload;
|
|
559
660
|
// Don't set as instance property
|
|
560
661
|
delete options.onload;
|
|
561
|
-
_this = _super.call(this, options)
|
|
662
|
+
_this = _super.call(this, __assign(__assign({}, options), {
|
|
663
|
+
// Set a default duration so that the super constructor doesn't throw an
|
|
664
|
+
// error
|
|
665
|
+
duration: (_a = options.duration) !== null && _a !== void 0 ? _a : 0 })) || this;
|
|
562
666
|
_this._initialized = false;
|
|
563
667
|
_this._sourceStartTime = options.sourceStartTime || 0;
|
|
564
668
|
applyOptions(options, _this);
|
|
565
669
|
var load = function () {
|
|
566
670
|
// TODO: && ?
|
|
567
|
-
if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0)
|
|
671
|
+
if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0) {
|
|
568
672
|
throw new Error('Invalid options.duration or options.sourceStartTime');
|
|
673
|
+
}
|
|
569
674
|
_this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
|
|
570
675
|
_this.duration = _this._unstretchedDuration / (_this.playbackRate);
|
|
571
676
|
// onload will use `this`, and can't bind itself because it's before
|
|
572
677
|
// super()
|
|
573
678
|
onload && onload.bind(_this)(_this.source, options);
|
|
574
679
|
};
|
|
575
|
-
if (_this.source.readyState >= 2)
|
|
680
|
+
if (_this.source.readyState >= 2) {
|
|
576
681
|
// this frame's data is available now
|
|
577
682
|
load();
|
|
578
|
-
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
579
685
|
// when this frame's data is available
|
|
580
686
|
_this.source.addEventListener('loadedmetadata', load);
|
|
687
|
+
}
|
|
581
688
|
_this.source.addEventListener('durationchange', function () {
|
|
582
689
|
_this.duration = options.duration || (_this.source.duration - _this.sourceStartTime);
|
|
583
690
|
});
|
|
584
691
|
return _this;
|
|
585
692
|
}
|
|
693
|
+
MixedAudioSource.prototype.whenReady = function () {
|
|
694
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
695
|
+
var _this = this;
|
|
696
|
+
return __generator(this, function (_a) {
|
|
697
|
+
switch (_a.label) {
|
|
698
|
+
case 0: return [4 /*yield*/, _super.prototype.whenReady.call(this)];
|
|
699
|
+
case 1:
|
|
700
|
+
_a.sent();
|
|
701
|
+
if (!(this.source.readyState < 4)) return [3 /*break*/, 3];
|
|
702
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
703
|
+
_this.source.addEventListener('canplaythrough', resolve);
|
|
704
|
+
})];
|
|
705
|
+
case 2:
|
|
706
|
+
_a.sent();
|
|
707
|
+
_a.label = 3;
|
|
708
|
+
case 3: return [2 /*return*/];
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
};
|
|
586
713
|
MixedAudioSource.prototype.attach = function (movie) {
|
|
587
714
|
var _this = this;
|
|
588
715
|
_super.prototype.attach.call(this, movie);
|
|
589
|
-
subscribe(movie, 'movie.seek', function () {
|
|
590
|
-
if (_this.currentTime < 0 || _this.currentTime >= _this.duration)
|
|
591
|
-
return;
|
|
592
|
-
_this.source.currentTime = _this.currentTime + _this.sourceStartTime;
|
|
593
|
-
});
|
|
594
716
|
// TODO: on unattach?
|
|
595
|
-
subscribe(movie, '
|
|
596
|
-
// Connect to new destination if
|
|
717
|
+
subscribe(movie, 'audiodestinationupdate', function (event) {
|
|
718
|
+
// Connect to new destination if immediately connected to the existing
|
|
597
719
|
// destination.
|
|
598
720
|
if (_this._connectedToDestination) {
|
|
599
721
|
_this.audioNode.disconnect(movie.actx.destination);
|
|
@@ -612,8 +734,9 @@ function AudioSourceMixin(superclass) {
|
|
|
612
734
|
var oldDisconnect = this._audioNode.disconnect.bind(this.audioNode);
|
|
613
735
|
this._audioNode.disconnect = function (destination, output, input) {
|
|
614
736
|
if (_this._connectedToDestination &&
|
|
615
|
-
destination === movie.actx.destination)
|
|
737
|
+
destination === movie.actx.destination) {
|
|
616
738
|
_this._connectedToDestination = false;
|
|
739
|
+
}
|
|
617
740
|
return oldDisconnect(destination, output, input);
|
|
618
741
|
};
|
|
619
742
|
// Connect to actx.destination by default (can be rewired by user)
|
|
@@ -629,6 +752,10 @@ function AudioSourceMixin(superclass) {
|
|
|
629
752
|
this.source.currentTime = this.currentTime + this.sourceStartTime;
|
|
630
753
|
this.source.play();
|
|
631
754
|
};
|
|
755
|
+
MixedAudioSource.prototype.seek = function (time) {
|
|
756
|
+
_super.prototype.seek.call(this, time);
|
|
757
|
+
this.source.currentTime = this.currentTime + this.sourceStartTime;
|
|
758
|
+
};
|
|
632
759
|
MixedAudioSource.prototype.render = function () {
|
|
633
760
|
_super.prototype.render.call(this);
|
|
634
761
|
// TODO: implement Issue: Create built-in audio node to support built-in
|
|
@@ -638,6 +765,7 @@ function AudioSourceMixin(superclass) {
|
|
|
638
765
|
this.source.playbackRate = val(this, 'playbackRate', this.currentTime);
|
|
639
766
|
};
|
|
640
767
|
MixedAudioSource.prototype.stop = function () {
|
|
768
|
+
_super.prototype.stop.call(this);
|
|
641
769
|
this.source.pause();
|
|
642
770
|
};
|
|
643
771
|
Object.defineProperty(MixedAudioSource.prototype, "audioNode", {
|
|
@@ -656,8 +784,9 @@ function AudioSourceMixin(superclass) {
|
|
|
656
784
|
},
|
|
657
785
|
set: function (value) {
|
|
658
786
|
this._playbackRate = value;
|
|
659
|
-
if (this._unstretchedDuration !== undefined)
|
|
787
|
+
if (this._unstretchedDuration !== undefined) {
|
|
660
788
|
this.duration = this._unstretchedDuration / value;
|
|
789
|
+
}
|
|
661
790
|
},
|
|
662
791
|
enumerable: false,
|
|
663
792
|
configurable: true
|
|
@@ -693,6 +822,18 @@ function AudioSourceMixin(superclass) {
|
|
|
693
822
|
enumerable: false,
|
|
694
823
|
configurable: true
|
|
695
824
|
});
|
|
825
|
+
Object.defineProperty(MixedAudioSource.prototype, "ready", {
|
|
826
|
+
get: function () {
|
|
827
|
+
// Typescript doesn't support `super.ready` when targeting es5
|
|
828
|
+
var superReady = Object.getOwnPropertyDescriptor(superclass.prototype, 'ready').get.call(this);
|
|
829
|
+
return superReady && this.source.readyState === 4;
|
|
830
|
+
},
|
|
831
|
+
enumerable: false,
|
|
832
|
+
configurable: true
|
|
833
|
+
});
|
|
834
|
+
/**
|
|
835
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
836
|
+
*/
|
|
696
837
|
MixedAudioSource.prototype.getDefaultOptions = function () {
|
|
697
838
|
return __assign(__assign({}, superclass.prototype.getDefaultOptions()), { source: undefined, sourceStartTime: 0, duration: undefined, muted: false, volume: 1, playbackRate: 1 });
|
|
698
839
|
};
|
|
@@ -715,56 +856,74 @@ var Base = /** @class */ (function () {
|
|
|
715
856
|
* movie's timeline
|
|
716
857
|
*/
|
|
717
858
|
function Base(options) {
|
|
859
|
+
if (options.duration === null || options.duration === undefined) {
|
|
860
|
+
throw new Error('Property "duration" is required in BaseOptions');
|
|
861
|
+
}
|
|
862
|
+
if (options.startTime === null || options.startTime === undefined) {
|
|
863
|
+
throw new Error('Property "startTime" is required in BaseOptions');
|
|
864
|
+
}
|
|
718
865
|
// Set startTime and duration properties manually, because they are
|
|
719
866
|
// readonly. applyOptions ignores readonly properties.
|
|
720
867
|
this._startTime = options.startTime;
|
|
721
868
|
this._duration = options.duration;
|
|
722
|
-
// Proxy that will be returned by constructor (for sending 'modified'
|
|
723
|
-
// events).
|
|
724
|
-
var newThis = watchPublic(this);
|
|
725
|
-
// Don't send updates when initializing, so use this instead of newThis
|
|
726
869
|
applyOptions(options, this);
|
|
727
870
|
// Whether this layer is currently being rendered
|
|
728
871
|
this.active = false;
|
|
729
872
|
this.enabled = true;
|
|
730
|
-
this._occurrenceCount = 0; // no
|
|
873
|
+
this._occurrenceCount = 0; // no occurrences in parent
|
|
731
874
|
this._movie = null;
|
|
732
|
-
// Propogate up to target
|
|
733
|
-
subscribe(newThis, 'layer.change', function (event) {
|
|
734
|
-
var typeOfChange = event.type.substring(event.type.lastIndexOf('.') + 1);
|
|
735
|
-
var type = "movie.change.layer." + typeOfChange;
|
|
736
|
-
publish(newThis._movie, type, __assign(__assign({}, event), { target: newThis._movie, type: type }));
|
|
737
|
-
});
|
|
738
|
-
return newThis;
|
|
739
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* Wait until this layer is ready to render
|
|
878
|
+
*/
|
|
879
|
+
Base.prototype.whenReady = function () {
|
|
880
|
+
return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
881
|
+
return [2 /*return*/];
|
|
882
|
+
}); });
|
|
883
|
+
}; // eslint-disable-line @typescript-eslint/no-empty-function
|
|
740
884
|
/**
|
|
741
885
|
* Attaches this layer to `movie` if not already attached.
|
|
742
886
|
* @ignore
|
|
743
887
|
*/
|
|
744
888
|
Base.prototype.tryAttach = function (movie) {
|
|
745
|
-
if (this._occurrenceCount === 0)
|
|
889
|
+
if (this._occurrenceCount === 0) {
|
|
746
890
|
this.attach(movie);
|
|
891
|
+
}
|
|
747
892
|
this._occurrenceCount++;
|
|
748
893
|
};
|
|
894
|
+
/**
|
|
895
|
+
* Attaches this layer to `movie`
|
|
896
|
+
*
|
|
897
|
+
* Called when the layer is added to a movie's `layers` array.
|
|
898
|
+
*
|
|
899
|
+
* @param movie The movie to attach to
|
|
900
|
+
*/
|
|
749
901
|
Base.prototype.attach = function (movie) {
|
|
750
902
|
this._movie = movie;
|
|
751
903
|
};
|
|
752
904
|
/**
|
|
753
|
-
*
|
|
905
|
+
* Detaches this layer from its movie if the number of times `tryDetach` has
|
|
754
906
|
* been called (including this call) equals the number of times `tryAttach`
|
|
755
907
|
* has been called.
|
|
756
908
|
*
|
|
757
909
|
* @ignore
|
|
758
910
|
*/
|
|
759
911
|
Base.prototype.tryDetach = function () {
|
|
760
|
-
if (this.movie === null)
|
|
912
|
+
if (this.movie === null) {
|
|
761
913
|
throw new Error('No movie to detach from');
|
|
914
|
+
}
|
|
762
915
|
this._occurrenceCount--;
|
|
763
916
|
// If this layer occurs in another place in a `layers` array, do not unset
|
|
764
917
|
// _movie. (For calling `unshift` on the `layers` proxy)
|
|
765
|
-
if (this._occurrenceCount === 0)
|
|
918
|
+
if (this._occurrenceCount === 0) {
|
|
766
919
|
this.detach();
|
|
920
|
+
}
|
|
767
921
|
};
|
|
922
|
+
/**
|
|
923
|
+
* Detaches this layer from its movie
|
|
924
|
+
*
|
|
925
|
+
* Called when the layer is removed from a movie's `layers` array.
|
|
926
|
+
*/
|
|
768
927
|
Base.prototype.detach = function () {
|
|
769
928
|
this._movie = null;
|
|
770
929
|
};
|
|
@@ -772,14 +931,40 @@ var Base = /** @class */ (function () {
|
|
|
772
931
|
* Called when the layer is activated
|
|
773
932
|
*/
|
|
774
933
|
Base.prototype.start = function () { }; // eslint-disable-line @typescript-eslint/no-empty-function
|
|
934
|
+
/**
|
|
935
|
+
* Update {@link currentTime} when seeking
|
|
936
|
+
*
|
|
937
|
+
* This method is called when the movie seeks to a new time at the request of
|
|
938
|
+
* the user. {@link progress} is called when the movie's `currentTime` is
|
|
939
|
+
* updated due to playback.
|
|
940
|
+
*
|
|
941
|
+
* @param time - The new time in the layer
|
|
942
|
+
*/
|
|
943
|
+
Base.prototype.seek = function (time) {
|
|
944
|
+
this._currentTime = time;
|
|
945
|
+
};
|
|
946
|
+
/**
|
|
947
|
+
* Update {@link currentTime} due to playback
|
|
948
|
+
*
|
|
949
|
+
* This method is called when the movie's `currentTime` is updated due to
|
|
950
|
+
* playback. {@link seek} is called when the movie seeks to a new time at the
|
|
951
|
+
* request of the user.
|
|
952
|
+
*
|
|
953
|
+
* @param time - The new time in the layer
|
|
954
|
+
*/
|
|
955
|
+
Base.prototype.progress = function (time) {
|
|
956
|
+
this._currentTime = time;
|
|
957
|
+
};
|
|
775
958
|
/**
|
|
776
959
|
* Called when the movie renders and the layer is active
|
|
777
960
|
*/
|
|
778
961
|
Base.prototype.render = function () { }; // eslint-disable-line @typescript-eslint/no-empty-function
|
|
779
962
|
/**
|
|
780
|
-
|
|
963
|
+
* Called when the layer is deactivated
|
|
781
964
|
*/
|
|
782
|
-
Base.prototype.stop = function () {
|
|
965
|
+
Base.prototype.stop = function () {
|
|
966
|
+
this._currentTime = undefined;
|
|
967
|
+
};
|
|
783
968
|
Object.defineProperty(Base.prototype, "parent", {
|
|
784
969
|
// TODO: is this needed?
|
|
785
970
|
get: function () {
|
|
@@ -790,6 +975,7 @@ var Base = /** @class */ (function () {
|
|
|
790
975
|
});
|
|
791
976
|
Object.defineProperty(Base.prototype, "startTime", {
|
|
792
977
|
/**
|
|
978
|
+
* The time in the movie at which this layer starts (in seconds)
|
|
793
979
|
*/
|
|
794
980
|
get: function () {
|
|
795
981
|
return this._startTime;
|
|
@@ -802,17 +988,17 @@ var Base = /** @class */ (function () {
|
|
|
802
988
|
});
|
|
803
989
|
Object.defineProperty(Base.prototype, "currentTime", {
|
|
804
990
|
/**
|
|
805
|
-
* The current time of the movie relative to this layer
|
|
991
|
+
* The current time of the movie relative to this layer (in seconds)
|
|
806
992
|
*/
|
|
807
993
|
get: function () {
|
|
808
|
-
return this.
|
|
809
|
-
: undefined;
|
|
994
|
+
return this._currentTime;
|
|
810
995
|
},
|
|
811
996
|
enumerable: false,
|
|
812
997
|
configurable: true
|
|
813
998
|
});
|
|
814
999
|
Object.defineProperty(Base.prototype, "duration", {
|
|
815
1000
|
/**
|
|
1001
|
+
* The duration of this layer (in seconds)
|
|
816
1002
|
*/
|
|
817
1003
|
get: function () {
|
|
818
1004
|
return this._duration;
|
|
@@ -823,6 +1009,16 @@ var Base = /** @class */ (function () {
|
|
|
823
1009
|
enumerable: false,
|
|
824
1010
|
configurable: true
|
|
825
1011
|
});
|
|
1012
|
+
Object.defineProperty(Base.prototype, "ready", {
|
|
1013
|
+
/**
|
|
1014
|
+
* `true` if this layer is ready to be rendered, `false` otherwise
|
|
1015
|
+
*/
|
|
1016
|
+
get: function () {
|
|
1017
|
+
return true;
|
|
1018
|
+
},
|
|
1019
|
+
enumerable: false,
|
|
1020
|
+
configurable: true
|
|
1021
|
+
});
|
|
826
1022
|
Object.defineProperty(Base.prototype, "movie", {
|
|
827
1023
|
get: function () {
|
|
828
1024
|
return this._movie;
|
|
@@ -830,6 +1026,9 @@ var Base = /** @class */ (function () {
|
|
|
830
1026
|
enumerable: false,
|
|
831
1027
|
configurable: true
|
|
832
1028
|
});
|
|
1029
|
+
/**
|
|
1030
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1031
|
+
*/
|
|
833
1032
|
Base.prototype.getDefaultOptions = function () {
|
|
834
1033
|
return {
|
|
835
1034
|
startTime: undefined,
|
|
@@ -846,6 +1045,7 @@ Base.prototype.propertyFilters = {};
|
|
|
846
1045
|
|
|
847
1046
|
// TODO: rename to something more consistent with the naming convention of Visual and VisualSourceMixin
|
|
848
1047
|
/**
|
|
1048
|
+
* Layer for an HTML audio element
|
|
849
1049
|
* @extends AudioSource
|
|
850
1050
|
*/
|
|
851
1051
|
var Audio = /** @class */ (function (_super) {
|
|
@@ -854,11 +1054,21 @@ var Audio = /** @class */ (function (_super) {
|
|
|
854
1054
|
* Creates an audio layer
|
|
855
1055
|
*/
|
|
856
1056
|
function Audio(options) {
|
|
857
|
-
var _this =
|
|
858
|
-
if (
|
|
1057
|
+
var _this = this;
|
|
1058
|
+
if (typeof options.source === 'string') {
|
|
1059
|
+
var audio = document.createElement('audio');
|
|
1060
|
+
audio.src = options.source;
|
|
1061
|
+
options.source = audio;
|
|
1062
|
+
}
|
|
1063
|
+
_this = _super.call(this, options) || this;
|
|
1064
|
+
if (_this.duration === undefined) {
|
|
859
1065
|
_this.duration = (_this).source.duration - _this.sourceStartTime;
|
|
1066
|
+
}
|
|
860
1067
|
return _this;
|
|
861
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1071
|
+
*/
|
|
862
1072
|
Audio.prototype.getDefaultOptions = function () {
|
|
863
1073
|
return __assign(__assign({}, Object.getPrototypeOf(this).getDefaultOptions()), {
|
|
864
1074
|
/**
|
|
@@ -870,40 +1080,101 @@ var Audio = /** @class */ (function (_super) {
|
|
|
870
1080
|
return Audio;
|
|
871
1081
|
}(AudioSourceMixin(Base)));
|
|
872
1082
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
_this
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1083
|
+
var CustomArrayListener = /** @class */ (function () {
|
|
1084
|
+
function CustomArrayListener() {
|
|
1085
|
+
}
|
|
1086
|
+
return CustomArrayListener;
|
|
1087
|
+
}());
|
|
1088
|
+
/**
|
|
1089
|
+
* An array that notifies a listener when items are added or removed.
|
|
1090
|
+
*/
|
|
1091
|
+
var CustomArray = /** @class */ (function (_super) {
|
|
1092
|
+
__extends(CustomArray, _super);
|
|
1093
|
+
function CustomArray(target, listener) {
|
|
1094
|
+
var _this = _super.call(this) || this;
|
|
1095
|
+
for (var _i = 0, target_1 = target; _i < target_1.length; _i++) {
|
|
1096
|
+
var item = target_1[_i];
|
|
1097
|
+
listener.onAdd(item);
|
|
1098
|
+
}
|
|
1099
|
+
// Create proxy
|
|
1100
|
+
return new Proxy(target, {
|
|
888
1101
|
deleteProperty: function (target, property) {
|
|
889
1102
|
var value = target[property];
|
|
890
|
-
value.detach();
|
|
891
1103
|
delete target[property];
|
|
1104
|
+
listener.onRemove(value);
|
|
892
1105
|
return true;
|
|
893
1106
|
},
|
|
894
1107
|
set: function (target, property, value) {
|
|
1108
|
+
var oldValue = target[property];
|
|
1109
|
+
target[property] = value;
|
|
1110
|
+
// Check if property is a number (index)
|
|
895
1111
|
if (!isNaN(Number(property))) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1112
|
+
if (oldValue !== undefined) {
|
|
1113
|
+
listener.onRemove(oldValue);
|
|
1114
|
+
}
|
|
1115
|
+
listener.onAdd(value);
|
|
900
1116
|
}
|
|
901
|
-
target[property] = value;
|
|
902
1117
|
return true;
|
|
903
1118
|
}
|
|
904
1119
|
});
|
|
1120
|
+
}
|
|
1121
|
+
return CustomArray;
|
|
1122
|
+
}(Array));
|
|
1123
|
+
|
|
1124
|
+
// eslint-disable-next-line no-use-before-define
|
|
1125
|
+
var VisualEffectsListener = /** @class */ (function (_super) {
|
|
1126
|
+
__extends(VisualEffectsListener, _super);
|
|
1127
|
+
// eslint-disable-next-line no-use-before-define
|
|
1128
|
+
function VisualEffectsListener(layer) {
|
|
1129
|
+
var _this = _super.call(this) || this;
|
|
1130
|
+
_this._layer = layer;
|
|
905
1131
|
return _this;
|
|
906
1132
|
}
|
|
1133
|
+
VisualEffectsListener.prototype.onAdd = function (effect) {
|
|
1134
|
+
effect.tryAttach(this._layer);
|
|
1135
|
+
};
|
|
1136
|
+
VisualEffectsListener.prototype.onRemove = function (effect) {
|
|
1137
|
+
effect.tryDetach();
|
|
1138
|
+
};
|
|
1139
|
+
return VisualEffectsListener;
|
|
1140
|
+
}(CustomArrayListener));
|
|
1141
|
+
var VisualEffects = /** @class */ (function (_super) {
|
|
1142
|
+
__extends(VisualEffects, _super);
|
|
1143
|
+
// eslint-disable-next-line no-use-before-define
|
|
1144
|
+
function VisualEffects(target, layer) {
|
|
1145
|
+
return _super.call(this, target, new VisualEffectsListener(layer)) || this;
|
|
1146
|
+
}
|
|
1147
|
+
return VisualEffects;
|
|
1148
|
+
}(CustomArray));
|
|
1149
|
+
/** Any layer that renders to a canvas */
|
|
1150
|
+
var Visual = /** @class */ (function (_super) {
|
|
1151
|
+
__extends(Visual, _super);
|
|
1152
|
+
/**
|
|
1153
|
+
* Creates a visual layer
|
|
1154
|
+
*/
|
|
1155
|
+
function Visual(options) {
|
|
1156
|
+
var _this = _super.call(this, options) || this;
|
|
1157
|
+
applyOptions(options, _this);
|
|
1158
|
+
_this.canvas = document.createElement('canvas');
|
|
1159
|
+
_this.cctx = _this.canvas.getContext('2d');
|
|
1160
|
+
_this.effects = new VisualEffects([], _this);
|
|
1161
|
+
return _this;
|
|
1162
|
+
}
|
|
1163
|
+
Visual.prototype.whenReady = function () {
|
|
1164
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1165
|
+
return __generator(this, function (_a) {
|
|
1166
|
+
switch (_a.label) {
|
|
1167
|
+
case 0: return [4 /*yield*/, _super.prototype.whenReady.call(this)];
|
|
1168
|
+
case 1:
|
|
1169
|
+
_a.sent();
|
|
1170
|
+
return [4 /*yield*/, Promise.all(this.effects.map(function (effect) { return effect.whenReady(); }))];
|
|
1171
|
+
case 2:
|
|
1172
|
+
_a.sent();
|
|
1173
|
+
return [2 /*return*/];
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
});
|
|
1177
|
+
};
|
|
907
1178
|
/**
|
|
908
1179
|
* Render visual output
|
|
909
1180
|
*/
|
|
@@ -911,8 +1182,9 @@ var Visual = /** @class */ (function (_super) {
|
|
|
911
1182
|
// Prevent empty canvas errors if the width or height is 0
|
|
912
1183
|
var width = val(this, 'width', this.currentTime);
|
|
913
1184
|
var height = val(this, 'height', this.currentTime);
|
|
914
|
-
if (width === 0 || height === 0)
|
|
1185
|
+
if (width === 0 || height === 0) {
|
|
915
1186
|
return;
|
|
1187
|
+
}
|
|
916
1188
|
this.beginRender();
|
|
917
1189
|
this.doRender();
|
|
918
1190
|
this.endRender();
|
|
@@ -943,20 +1215,22 @@ var Visual = /** @class */ (function (_super) {
|
|
|
943
1215
|
Visual.prototype.endRender = function () {
|
|
944
1216
|
var w = val(this, 'width', this.currentTime) || val(this.movie, 'width', this.movie.currentTime);
|
|
945
1217
|
var h = val(this, 'height', this.currentTime) || val(this.movie, 'height', this.movie.currentTime);
|
|
946
|
-
if (w * h > 0)
|
|
1218
|
+
if (w * h > 0) {
|
|
947
1219
|
this._applyEffects();
|
|
1220
|
+
}
|
|
948
1221
|
// else InvalidStateError for drawing zero-area image in some effects, right?
|
|
949
1222
|
};
|
|
950
1223
|
Visual.prototype._applyEffects = function () {
|
|
951
1224
|
for (var i = 0; i < this.effects.length; i++) {
|
|
952
1225
|
var effect = this.effects[i];
|
|
953
|
-
if (effect && effect.enabled)
|
|
1226
|
+
if (effect && effect.enabled) {
|
|
954
1227
|
// Pass relative time
|
|
955
1228
|
effect.apply(this, this.movie.currentTime - this.startTime);
|
|
1229
|
+
}
|
|
956
1230
|
}
|
|
957
1231
|
};
|
|
958
1232
|
/**
|
|
959
|
-
*
|
|
1233
|
+
* Convenience method for <code>effects.push()</code>
|
|
960
1234
|
* @param effect
|
|
961
1235
|
* @return the layer (for chaining)
|
|
962
1236
|
*/
|
|
@@ -964,6 +1238,18 @@ var Visual = /** @class */ (function (_super) {
|
|
|
964
1238
|
this.effects.push(effect);
|
|
965
1239
|
return this;
|
|
966
1240
|
};
|
|
1241
|
+
Object.defineProperty(Visual.prototype, "ready", {
|
|
1242
|
+
get: function () {
|
|
1243
|
+
// Typescript doesn't support `super.ready` when targeting es5
|
|
1244
|
+
var superReady = Object.getOwnPropertyDescriptor(Base.prototype, 'ready').get.call(this);
|
|
1245
|
+
return superReady && this.effects.every(function (effect) { return effect.ready; });
|
|
1246
|
+
},
|
|
1247
|
+
enumerable: false,
|
|
1248
|
+
configurable: true
|
|
1249
|
+
});
|
|
1250
|
+
/**
|
|
1251
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1252
|
+
*/
|
|
967
1253
|
Visual.prototype.getDefaultOptions = function () {
|
|
968
1254
|
return __assign(__assign({}, Base.prototype.getDefaultOptions()), {
|
|
969
1255
|
/**
|
|
@@ -986,13 +1272,13 @@ var Visual = /** @class */ (function (_super) {
|
|
|
986
1272
|
height: null,
|
|
987
1273
|
/**
|
|
988
1274
|
* @name module:layer.Visual#background
|
|
989
|
-
* @desc The
|
|
1275
|
+
* @desc The color code for the background, or <code>null</code> for
|
|
990
1276
|
* transparency
|
|
991
1277
|
*/
|
|
992
1278
|
background: null,
|
|
993
1279
|
/**
|
|
994
1280
|
* @name module:layer.Visual#border
|
|
995
|
-
* @desc The
|
|
1281
|
+
* @desc The border style, or <code>null</code> for no border
|
|
996
1282
|
*/
|
|
997
1283
|
border: null,
|
|
998
1284
|
/**
|
|
@@ -1022,17 +1308,60 @@ function VisualSourceMixin(superclass) {
|
|
|
1022
1308
|
var MixedVisualSource = /** @class */ (function (_super) {
|
|
1023
1309
|
__extends(MixedVisualSource, _super);
|
|
1024
1310
|
function MixedVisualSource(options) {
|
|
1025
|
-
var _this =
|
|
1311
|
+
var _this = this;
|
|
1312
|
+
if (!options.source) {
|
|
1313
|
+
throw new Error('Property "source" is required in options');
|
|
1314
|
+
}
|
|
1315
|
+
_this = _super.call(this, options) || this;
|
|
1026
1316
|
applyOptions(options, _this);
|
|
1027
1317
|
return _this;
|
|
1028
1318
|
}
|
|
1319
|
+
MixedVisualSource.prototype.whenReady = function () {
|
|
1320
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1321
|
+
var _this = this;
|
|
1322
|
+
return __generator(this, function (_a) {
|
|
1323
|
+
switch (_a.label) {
|
|
1324
|
+
case 0: return [4 /*yield*/, _super.prototype.whenReady.call(this)];
|
|
1325
|
+
case 1:
|
|
1326
|
+
_a.sent();
|
|
1327
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
1328
|
+
if (_this.source instanceof HTMLImageElement) {
|
|
1329
|
+
// The source is an image; wait for it to load
|
|
1330
|
+
if (_this.source.complete) {
|
|
1331
|
+
resolve();
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
_this.source.addEventListener('load', function () {
|
|
1335
|
+
resolve();
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
else {
|
|
1340
|
+
// The source is a video; wait for the first frame to load
|
|
1341
|
+
if (_this.source.readyState === 4) {
|
|
1342
|
+
resolve();
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
_this.source.addEventListener('canplaythrough', function () {
|
|
1346
|
+
resolve();
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
})];
|
|
1351
|
+
case 2:
|
|
1352
|
+
_a.sent();
|
|
1353
|
+
return [2 /*return*/];
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
});
|
|
1357
|
+
};
|
|
1029
1358
|
MixedVisualSource.prototype.doRender = function () {
|
|
1030
1359
|
// Clear/fill background
|
|
1031
1360
|
_super.prototype.doRender.call(this);
|
|
1032
1361
|
/*
|
|
1033
1362
|
* Source dimensions crop the image. Dest dimensions set the size that
|
|
1034
1363
|
* the image will be rendered at *on the layer*. Note that this is
|
|
1035
|
-
* different
|
|
1364
|
+
* different from the layer dimensions (`this.width` and `this.height`).
|
|
1036
1365
|
* The main reason this distinction exists is so that an image layer can
|
|
1037
1366
|
* be rotated without being cropped (see iss #46).
|
|
1038
1367
|
*/
|
|
@@ -1040,6 +1369,21 @@ function VisualSourceMixin(superclass) {
|
|
|
1040
1369
|
// `destX` and `destY` are relative to the layer
|
|
1041
1370
|
val(this, 'destX', this.currentTime), val(this, 'destY', this.currentTime), val(this, 'destWidth', this.currentTime), val(this, 'destHeight', this.currentTime));
|
|
1042
1371
|
};
|
|
1372
|
+
Object.defineProperty(MixedVisualSource.prototype, "ready", {
|
|
1373
|
+
get: function () {
|
|
1374
|
+
// Typescript doesn't support `super.ready` when targeting es5
|
|
1375
|
+
var superReady = Object.getOwnPropertyDescriptor(superclass.prototype, 'ready').get.call(this);
|
|
1376
|
+
var sourceReady = this.source instanceof HTMLImageElement
|
|
1377
|
+
? this.source.complete
|
|
1378
|
+
: this.source.readyState === 4;
|
|
1379
|
+
return superReady && sourceReady;
|
|
1380
|
+
},
|
|
1381
|
+
enumerable: false,
|
|
1382
|
+
configurable: true
|
|
1383
|
+
});
|
|
1384
|
+
/**
|
|
1385
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1386
|
+
*/
|
|
1043
1387
|
MixedVisualSource.prototype.getDefaultOptions = function () {
|
|
1044
1388
|
return __assign(__assign({}, superclass.prototype.getDefaultOptions()), { source: undefined, sourceX: 0, sourceY: 0, sourceWidth: undefined, sourceHeight: undefined, destX: 0, destY: 0, destWidth: undefined, destHeight: undefined });
|
|
1045
1389
|
};
|
|
@@ -1067,27 +1411,40 @@ function VisualSourceMixin(superclass) {
|
|
|
1067
1411
|
// instead. (TODO: fact check)
|
|
1068
1412
|
/* eslint-disable eqeqeq */
|
|
1069
1413
|
return destWidth != undefined
|
|
1070
|
-
? destWidth
|
|
1414
|
+
? destWidth
|
|
1415
|
+
: val(this, 'sourceWidth', this.currentTime);
|
|
1071
1416
|
}, destHeight: function (destHeight) {
|
|
1072
1417
|
/* eslint-disable eqeqeq */
|
|
1073
1418
|
return destHeight != undefined
|
|
1074
|
-
? destHeight
|
|
1419
|
+
? destHeight
|
|
1420
|
+
: val(this, 'sourceHeight', this.currentTime);
|
|
1075
1421
|
}, width: function (width) {
|
|
1076
1422
|
/* eslint-disable eqeqeq */
|
|
1077
1423
|
return width != undefined
|
|
1078
|
-
? width
|
|
1424
|
+
? width
|
|
1425
|
+
: val(this, 'destWidth', this.currentTime);
|
|
1079
1426
|
}, height: function (height) {
|
|
1080
1427
|
/* eslint-disable eqeqeq */
|
|
1081
1428
|
return height != undefined
|
|
1082
|
-
? height
|
|
1429
|
+
? height
|
|
1430
|
+
: val(this, 'destHeight', this.currentTime);
|
|
1083
1431
|
} });
|
|
1084
1432
|
return MixedVisualSource;
|
|
1085
1433
|
}
|
|
1086
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* Layer for an HTML image element
|
|
1437
|
+
* @extends VisualSource
|
|
1438
|
+
*/
|
|
1087
1439
|
var Image = /** @class */ (function (_super) {
|
|
1088
1440
|
__extends(Image, _super);
|
|
1089
|
-
function Image() {
|
|
1090
|
-
|
|
1441
|
+
function Image(options) {
|
|
1442
|
+
if (typeof (options.source) === 'string') {
|
|
1443
|
+
var img = document.createElement('img');
|
|
1444
|
+
img.src = options.source;
|
|
1445
|
+
options.source = img;
|
|
1446
|
+
}
|
|
1447
|
+
return _super.call(this, options) || this;
|
|
1091
1448
|
}
|
|
1092
1449
|
return Image;
|
|
1093
1450
|
}(VisualSourceMixin(Visual)));
|
|
@@ -1101,9 +1458,12 @@ var Text = /** @class */ (function (_super) {
|
|
|
1101
1458
|
// TODO: is textX necessary? it seems inconsistent, because you can't define
|
|
1102
1459
|
// width/height directly for a text layer
|
|
1103
1460
|
function Text(options) {
|
|
1104
|
-
var _this =
|
|
1461
|
+
var _this = this;
|
|
1462
|
+
if (!options.text) {
|
|
1463
|
+
throw new Error('Property "text" is required in TextOptions');
|
|
1464
|
+
}
|
|
1105
1465
|
// Default to no (transparent) background
|
|
1106
|
-
_super.call(this, __assign({ background: null }, options)) || this;
|
|
1466
|
+
_this = _super.call(this, __assign({ background: null }, options)) || this;
|
|
1107
1467
|
applyOptions(options, _this);
|
|
1108
1468
|
return _this;
|
|
1109
1469
|
// this._prevText = undefined;
|
|
@@ -1150,22 +1510,33 @@ var Text = /** @class */ (function (_super) {
|
|
|
1150
1510
|
document.body.removeChild(s);
|
|
1151
1511
|
return metrics;
|
|
1152
1512
|
} */
|
|
1513
|
+
/**
|
|
1514
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1515
|
+
*/
|
|
1153
1516
|
Text.prototype.getDefaultOptions = function () {
|
|
1154
|
-
return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: '#fff', textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr' });
|
|
1517
|
+
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' });
|
|
1155
1518
|
};
|
|
1156
1519
|
return Text;
|
|
1157
1520
|
}(Visual));
|
|
1158
1521
|
|
|
1159
|
-
// Use mixins instead of `extend`ing two classes (which isn't supported by
|
|
1160
|
-
// JavaScript).
|
|
1161
1522
|
/**
|
|
1523
|
+
* Layer for an HTML video element
|
|
1162
1524
|
* @extends AudioSource
|
|
1163
1525
|
* @extends VisualSource
|
|
1164
1526
|
*/
|
|
1165
1527
|
var Video = /** @class */ (function (_super) {
|
|
1166
1528
|
__extends(Video, _super);
|
|
1167
|
-
function Video() {
|
|
1168
|
-
|
|
1529
|
+
function Video(options) {
|
|
1530
|
+
var _a;
|
|
1531
|
+
if (typeof (options.source) === 'string') {
|
|
1532
|
+
var video = document.createElement('video');
|
|
1533
|
+
video.src = options.source;
|
|
1534
|
+
options.source = video;
|
|
1535
|
+
}
|
|
1536
|
+
return _super.call(this, __assign(__assign({}, options), {
|
|
1537
|
+
// Set a default duration so that the super constructor doesn't throw an
|
|
1538
|
+
// error
|
|
1539
|
+
duration: (_a = options.duration) !== null && _a !== void 0 ? _a : 0 })) || this;
|
|
1169
1540
|
}
|
|
1170
1541
|
return Video;
|
|
1171
1542
|
}(AudioSourceMixin(VisualSourceMixin(Visual))));
|
|
@@ -1175,6 +1546,7 @@ var Video = /** @class */ (function (_super) {
|
|
|
1175
1546
|
*/
|
|
1176
1547
|
|
|
1177
1548
|
var index = /*#__PURE__*/Object.freeze({
|
|
1549
|
+
__proto__: null,
|
|
1178
1550
|
AudioSourceMixin: AudioSourceMixin,
|
|
1179
1551
|
Audio: Audio,
|
|
1180
1552
|
Base: Base,
|
|
@@ -1190,46 +1562,48 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
1190
1562
|
*/
|
|
1191
1563
|
var Base$1 = /** @class */ (function () {
|
|
1192
1564
|
function Base() {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
newThis._target = null;
|
|
1197
|
-
// Propogate up to target
|
|
1198
|
-
subscribe(newThis, 'effect.change.modify', function (event) {
|
|
1199
|
-
if (!newThis._target)
|
|
1200
|
-
return;
|
|
1201
|
-
var type = newThis._target.type + ".change.effect.modify";
|
|
1202
|
-
publish(newThis._target, type, __assign(__assign({}, event), { target: newThis._target, source: newThis, type: type }));
|
|
1203
|
-
});
|
|
1204
|
-
return newThis;
|
|
1565
|
+
this.enabled = true;
|
|
1566
|
+
this._occurrenceCount = 0;
|
|
1567
|
+
this._target = null;
|
|
1205
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Wait until this effect is ready to be applied
|
|
1571
|
+
*/
|
|
1572
|
+
Base.prototype.whenReady = function () {
|
|
1573
|
+
return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1574
|
+
return [2 /*return*/];
|
|
1575
|
+
}); });
|
|
1576
|
+
}; // eslint-disable-line @typescript-eslint/no-empty-function
|
|
1206
1577
|
/**
|
|
1207
1578
|
* Attaches this effect to `target` if not already attached.
|
|
1208
1579
|
* @ignore
|
|
1209
1580
|
*/
|
|
1210
1581
|
Base.prototype.tryAttach = function (target) {
|
|
1211
|
-
if (this._occurrenceCount === 0)
|
|
1582
|
+
if (this._occurrenceCount === 0) {
|
|
1212
1583
|
this.attach(target);
|
|
1584
|
+
}
|
|
1213
1585
|
this._occurrenceCount++;
|
|
1214
1586
|
};
|
|
1215
1587
|
Base.prototype.attach = function (movie) {
|
|
1216
1588
|
this._target = movie;
|
|
1217
1589
|
};
|
|
1218
1590
|
/**
|
|
1219
|
-
*
|
|
1591
|
+
* Detaches this effect from its target if the number of times `tryDetach`
|
|
1220
1592
|
* has been called (including this call) equals the number of times
|
|
1221
1593
|
* `tryAttach` has been called.
|
|
1222
1594
|
*
|
|
1223
1595
|
* @ignore
|
|
1224
1596
|
*/
|
|
1225
1597
|
Base.prototype.tryDetach = function () {
|
|
1226
|
-
if (this._target === null)
|
|
1598
|
+
if (this._target === null) {
|
|
1227
1599
|
throw new Error('No movie to detach from');
|
|
1600
|
+
}
|
|
1228
1601
|
this._occurrenceCount--;
|
|
1229
1602
|
// If this effect occurs in another place in the containing array, do not
|
|
1230
1603
|
// unset _target. (For calling `unshift` on the `layers` proxy)
|
|
1231
|
-
if (this._occurrenceCount === 0)
|
|
1604
|
+
if (this._occurrenceCount === 0) {
|
|
1232
1605
|
this.detach();
|
|
1606
|
+
}
|
|
1233
1607
|
};
|
|
1234
1608
|
Base.prototype.detach = function () {
|
|
1235
1609
|
this._target = null;
|
|
@@ -1254,6 +1628,14 @@ var Base$1 = /** @class */ (function () {
|
|
|
1254
1628
|
enumerable: false,
|
|
1255
1629
|
configurable: true
|
|
1256
1630
|
});
|
|
1631
|
+
Object.defineProperty(Base.prototype, "ready", {
|
|
1632
|
+
/** `true` if this effect is ready to be applied */
|
|
1633
|
+
get: function () {
|
|
1634
|
+
return true;
|
|
1635
|
+
},
|
|
1636
|
+
enumerable: false,
|
|
1637
|
+
configurable: true
|
|
1638
|
+
});
|
|
1257
1639
|
Object.defineProperty(Base.prototype, "parent", {
|
|
1258
1640
|
get: function () {
|
|
1259
1641
|
return this._target;
|
|
@@ -1268,6 +1650,9 @@ var Base$1 = /** @class */ (function () {
|
|
|
1268
1650
|
enumerable: false,
|
|
1269
1651
|
configurable: true
|
|
1270
1652
|
});
|
|
1653
|
+
/**
|
|
1654
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
1655
|
+
*/
|
|
1271
1656
|
Base.prototype.getDefaultOptions = function () {
|
|
1272
1657
|
return {};
|
|
1273
1658
|
};
|
|
@@ -1335,16 +1720,18 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1335
1720
|
Shader.prototype._initGl = function () {
|
|
1336
1721
|
this._canvas = document.createElement('canvas');
|
|
1337
1722
|
var gl = this._canvas.getContext('webgl');
|
|
1338
|
-
if (gl === null)
|
|
1723
|
+
if (gl === null) {
|
|
1339
1724
|
throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.');
|
|
1725
|
+
}
|
|
1340
1726
|
this._gl = gl;
|
|
1341
1727
|
return gl;
|
|
1342
1728
|
};
|
|
1343
1729
|
Shader.prototype._initTextures = function (userUniforms, userTextures, sourceTextureOptions) {
|
|
1344
1730
|
var gl = this._gl;
|
|
1345
1731
|
var maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
|
|
1346
|
-
if (userTextures.length > maxTextures)
|
|
1732
|
+
if (userTextures.length > maxTextures) {
|
|
1347
1733
|
console.warn('Too many textures!');
|
|
1734
|
+
}
|
|
1348
1735
|
this._userTextures = {};
|
|
1349
1736
|
for (var name_1 in userTextures) {
|
|
1350
1737
|
var userOptions = userTextures[name_1];
|
|
@@ -1357,8 +1744,9 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1357
1744
|
* textures, without having to define multiple properties in the effect
|
|
1358
1745
|
* object.
|
|
1359
1746
|
*/
|
|
1360
|
-
if (userUniforms[name_1])
|
|
1361
|
-
throw new Error("Texture - uniform naming conflict: "
|
|
1747
|
+
if (userUniforms[name_1]) {
|
|
1748
|
+
throw new Error("Texture - uniform naming conflict: ".concat(name_1, "!"));
|
|
1749
|
+
}
|
|
1362
1750
|
// Add this as a "user uniform".
|
|
1363
1751
|
userUniforms[name_1] = '1i'; // texture pointer
|
|
1364
1752
|
}
|
|
@@ -1393,18 +1781,6 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1393
1781
|
this._uniformLocations[unprefixed] = gl.getUniformLocation(this._program, prefixed);
|
|
1394
1782
|
}
|
|
1395
1783
|
};
|
|
1396
|
-
// Not needed, right?
|
|
1397
|
-
/* watchWebGLOptions() {
|
|
1398
|
-
const pubChange = () => {
|
|
1399
|
-
this.publish("change", {});
|
|
1400
|
-
};
|
|
1401
|
-
for (let name in this._userTextures) {
|
|
1402
|
-
watch(this, name, pubChange);
|
|
1403
|
-
}
|
|
1404
|
-
for (let name in this._userUniforms) {
|
|
1405
|
-
watch(this, name, pubChange);
|
|
1406
|
-
}
|
|
1407
|
-
} */
|
|
1408
1784
|
Shader.prototype.apply = function (target, reltime) {
|
|
1409
1785
|
this._checkDimensions(target);
|
|
1410
1786
|
this._refreshGl();
|
|
@@ -1491,16 +1867,22 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1491
1867
|
i++;
|
|
1492
1868
|
}
|
|
1493
1869
|
};
|
|
1870
|
+
/**
|
|
1871
|
+
* Set the shader's uniforms.
|
|
1872
|
+
* @param target The movie or layer to apply the shader to.
|
|
1873
|
+
* @param reltime The relative time of the movie or layer.
|
|
1874
|
+
*/
|
|
1494
1875
|
Shader.prototype._prepareUniforms = function (target, reltime) {
|
|
1495
1876
|
var gl = this._gl;
|
|
1496
|
-
// Set the shader uniforms.
|
|
1497
1877
|
// Tell the shader we bound the texture to texture unit 0.
|
|
1498
1878
|
// All base (Shader class) uniforms are optional.
|
|
1499
|
-
if (this._uniformLocations.source)
|
|
1879
|
+
if (this._uniformLocations.source) {
|
|
1500
1880
|
gl.uniform1i(this._uniformLocations.source, 0);
|
|
1881
|
+
}
|
|
1501
1882
|
// All base (Shader class) uniforms are optional.
|
|
1502
|
-
if (this._uniformLocations.size)
|
|
1883
|
+
if (this._uniformLocations.size) {
|
|
1503
1884
|
gl.uniform2iv(this._uniformLocations.size, [target.canvas.width, target.canvas.height]);
|
|
1885
|
+
}
|
|
1504
1886
|
for (var unprefixed in this._userUniforms) {
|
|
1505
1887
|
var options = this._userUniforms[unprefixed];
|
|
1506
1888
|
var value = val(this, unprefixed, reltime);
|
|
@@ -1524,11 +1906,13 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1524
1906
|
/**
|
|
1525
1907
|
* Converts a value of a standard type for javascript to a standard type for
|
|
1526
1908
|
* GLSL
|
|
1909
|
+
*
|
|
1527
1910
|
* @param value - the raw value to prepare
|
|
1528
1911
|
* @param outputType - the WebGL type of |value|; example:
|
|
1529
1912
|
* <code>1f</code> for a float
|
|
1530
1913
|
* @param reltime - current time, relative to the target
|
|
1531
|
-
* @param [options]
|
|
1914
|
+
* @param [options]
|
|
1915
|
+
* @returns the prepared value
|
|
1532
1916
|
*/
|
|
1533
1917
|
Shader.prototype._prepareValue = function (value, outputType, reltime, options) {
|
|
1534
1918
|
if (options === void 0) { options = {}; }
|
|
@@ -1551,36 +1935,41 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1551
1935
|
var i = 0;
|
|
1552
1936
|
for (var name_4 in this._userTextures) {
|
|
1553
1937
|
var testValue = val(this, name_4, reltime);
|
|
1554
|
-
if (value === testValue)
|
|
1938
|
+
if (value === testValue) {
|
|
1555
1939
|
value = Shader.INTERNAL_TEXTURE_UNITS + i; // after the internal texture units
|
|
1940
|
+
}
|
|
1556
1941
|
i++;
|
|
1557
1942
|
}
|
|
1558
1943
|
}
|
|
1559
1944
|
if (outputType === '3fv') {
|
|
1560
1945
|
// allow 4-component vectors; TODO: why?
|
|
1561
|
-
if (Array.isArray(value) && (value.length === 3 || value.length === 4))
|
|
1946
|
+
if (Array.isArray(value) && (value.length === 3 || value.length === 4)) {
|
|
1562
1947
|
return value;
|
|
1948
|
+
}
|
|
1563
1949
|
// kind of loose so this can be changed if needed
|
|
1564
|
-
if (typeof value === 'object')
|
|
1950
|
+
if (typeof value === 'object') {
|
|
1565
1951
|
return [
|
|
1566
1952
|
value.r !== undefined ? value.r : def,
|
|
1567
1953
|
value.g !== undefined ? value.g : def,
|
|
1568
1954
|
value.b !== undefined ? value.b : def
|
|
1569
1955
|
];
|
|
1570
|
-
|
|
1956
|
+
}
|
|
1957
|
+
throw new Error("Invalid type: ".concat(outputType, " or value: ").concat(value));
|
|
1571
1958
|
}
|
|
1572
1959
|
if (outputType === '4fv') {
|
|
1573
|
-
if (Array.isArray(value) && value.length === 4)
|
|
1960
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
1574
1961
|
return value;
|
|
1962
|
+
}
|
|
1575
1963
|
// kind of loose so this can be changed if needed
|
|
1576
|
-
if (typeof value === 'object')
|
|
1964
|
+
if (typeof value === 'object') {
|
|
1577
1965
|
return [
|
|
1578
1966
|
value.r !== undefined ? value.r : def,
|
|
1579
1967
|
value.g !== undefined ? value.g : def,
|
|
1580
1968
|
value.b !== undefined ? value.b : def,
|
|
1581
1969
|
value.a !== undefined ? value.a : def
|
|
1582
1970
|
];
|
|
1583
|
-
|
|
1971
|
+
}
|
|
1972
|
+
throw new Error("Invalid type: ".concat(outputType, " or value: ").concat(value));
|
|
1584
1973
|
}
|
|
1585
1974
|
return value;
|
|
1586
1975
|
};
|
|
@@ -1673,8 +2062,9 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1673
2062
|
else {
|
|
1674
2063
|
// No, it's not a power of 2. Turn off mips and set
|
|
1675
2064
|
// wrapping to clamp to edge
|
|
1676
|
-
if (wrapS !== gl.CLAMP_TO_EDGE || wrapT !== gl.CLAMP_TO_EDGE)
|
|
2065
|
+
if (wrapS !== gl.CLAMP_TO_EDGE || wrapT !== gl.CLAMP_TO_EDGE) {
|
|
1677
2066
|
console.warn('Wrap mode is not CLAMP_TO_EDGE for a non-power-of-two texture. Defaulting to CLAMP_TO_EDGE');
|
|
2067
|
+
}
|
|
1678
2068
|
gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1679
2069
|
gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1680
2070
|
}
|
|
@@ -1726,7 +2116,6 @@ var Shader = /** @class */ (function (_super) {
|
|
|
1726
2116
|
Shader._IDENTITY_FRAGMENT_SOURCE = "\n precision mediump float;\n\n uniform sampler2D u_Source;\n\n varying highp vec2 v_TextureCoord;\n\n void main() {\n gl_FragColor = texture2D(u_Source, v_TextureCoord);\n }\n ";
|
|
1727
2117
|
return Shader;
|
|
1728
2118
|
}(Visual$1));
|
|
1729
|
-
// Shader.prototype.getpublicExcludes = () =>
|
|
1730
2119
|
var isPowerOf2 = function (value) { return (value && (value - 1)) === 0; };
|
|
1731
2120
|
|
|
1732
2121
|
/**
|
|
@@ -1901,6 +2290,35 @@ var EllipticalMask = /** @class */ (function (_super) {
|
|
|
1901
2290
|
return EllipticalMask;
|
|
1902
2291
|
}(Visual$1));
|
|
1903
2292
|
|
|
2293
|
+
var StackEffectsListener = /** @class */ (function (_super) {
|
|
2294
|
+
__extends(StackEffectsListener, _super);
|
|
2295
|
+
function StackEffectsListener(stack) {
|
|
2296
|
+
var _this = _super.call(this) || this;
|
|
2297
|
+
_this._stack = stack;
|
|
2298
|
+
return _this;
|
|
2299
|
+
}
|
|
2300
|
+
StackEffectsListener.prototype.onAdd = function (effect) {
|
|
2301
|
+
if (!this._stack.parent) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
effect.tryAttach(this._stack.parent);
|
|
2305
|
+
};
|
|
2306
|
+
StackEffectsListener.prototype.onRemove = function (effect) {
|
|
2307
|
+
if (!this._stack.parent) {
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
effect.tryDetach();
|
|
2311
|
+
};
|
|
2312
|
+
return StackEffectsListener;
|
|
2313
|
+
}(CustomArrayListener));
|
|
2314
|
+
var StackEffects = /** @class */ (function (_super) {
|
|
2315
|
+
__extends(StackEffects, _super);
|
|
2316
|
+
// eslint-disable-next-line no-use-before-define
|
|
2317
|
+
function StackEffects(target, stack) {
|
|
2318
|
+
return _super.call(this, target, new StackEffectsListener(stack)) || this;
|
|
2319
|
+
}
|
|
2320
|
+
return StackEffects;
|
|
2321
|
+
}(CustomArray));
|
|
1904
2322
|
/**
|
|
1905
2323
|
* A sequence of effects to apply, treated as one effect. This can be useful
|
|
1906
2324
|
* for defining reused effect sequences as one effect.
|
|
@@ -1909,48 +2327,28 @@ var Stack = /** @class */ (function (_super) {
|
|
|
1909
2327
|
__extends(Stack, _super);
|
|
1910
2328
|
function Stack(options) {
|
|
1911
2329
|
var _this = _super.call(this) || this;
|
|
1912
|
-
_this.
|
|
1913
|
-
// TODO: Throw 'change' events in handlers
|
|
1914
|
-
_this.effects = new Proxy(_this._effectsBack, {
|
|
1915
|
-
deleteProperty: function (target, property) {
|
|
1916
|
-
var value = target[property];
|
|
1917
|
-
value.detach(); // Detach effect from movie
|
|
1918
|
-
delete target[property];
|
|
1919
|
-
return true;
|
|
1920
|
-
},
|
|
1921
|
-
set: function (target, property, value) {
|
|
1922
|
-
// TODO: make sure type check works
|
|
1923
|
-
if (!isNaN(Number(property))) { // if property is a number (index)
|
|
1924
|
-
if (target[property])
|
|
1925
|
-
target[property].detach(); // Detach old effect from movie
|
|
1926
|
-
value.attach(this._target); // Attach effect to movie
|
|
1927
|
-
}
|
|
1928
|
-
target[property] = value;
|
|
1929
|
-
return true;
|
|
1930
|
-
}
|
|
1931
|
-
});
|
|
2330
|
+
_this.effects = new StackEffects(options.effects, _this);
|
|
1932
2331
|
options.effects.forEach(function (effect) { return _this.effects.push(effect); });
|
|
1933
2332
|
return _this;
|
|
1934
|
-
// TODO: Propogate 'change' events from children up
|
|
1935
2333
|
}
|
|
1936
2334
|
Stack.prototype.attach = function (movie) {
|
|
1937
2335
|
_super.prototype.attach.call(this, movie);
|
|
1938
2336
|
this.effects.filter(function (effect) { return !!effect; }).forEach(function (effect) {
|
|
1939
|
-
effect.
|
|
1940
|
-
effect.attach(movie);
|
|
2337
|
+
effect.tryAttach(movie);
|
|
1941
2338
|
});
|
|
1942
2339
|
};
|
|
1943
2340
|
Stack.prototype.detach = function () {
|
|
1944
2341
|
_super.prototype.detach.call(this);
|
|
1945
2342
|
this.effects.filter(function (effect) { return !!effect; }).forEach(function (effect) {
|
|
1946
|
-
effect.
|
|
2343
|
+
effect.tryDetach();
|
|
1947
2344
|
});
|
|
1948
2345
|
};
|
|
1949
2346
|
Stack.prototype.apply = function (target, reltime) {
|
|
1950
2347
|
for (var i = 0; i < this.effects.length; i++) {
|
|
1951
2348
|
var effect = this.effects[i];
|
|
1952
|
-
if (!effect)
|
|
2349
|
+
if (!effect) {
|
|
1953
2350
|
continue;
|
|
2351
|
+
}
|
|
1954
2352
|
effect.apply(target, reltime);
|
|
1955
2353
|
}
|
|
1956
2354
|
};
|
|
@@ -1969,7 +2367,7 @@ var Stack = /** @class */ (function (_super) {
|
|
|
1969
2367
|
* Applies a Gaussian blur
|
|
1970
2368
|
*/
|
|
1971
2369
|
// TODO: Improve performance
|
|
1972
|
-
// TODO: Make sure this is truly gaussian even though it
|
|
2370
|
+
// TODO: Make sure this is truly gaussian even though it doesn't require a
|
|
1973
2371
|
// standard deviation
|
|
1974
2372
|
var GaussianBlur = /** @class */ (function (_super) {
|
|
1975
2373
|
__extends(GaussianBlur, _super);
|
|
@@ -2014,9 +2412,10 @@ var GaussianBlurComponent = /** @class */ (function (_super) {
|
|
|
2014
2412
|
}
|
|
2015
2413
|
GaussianBlurComponent.prototype.apply = function (target, reltime) {
|
|
2016
2414
|
var radiusVal = val(this, 'radius', reltime);
|
|
2017
|
-
if (radiusVal !== this._radiusCache)
|
|
2415
|
+
if (radiusVal !== this._radiusCache) {
|
|
2018
2416
|
// Regenerate gaussian distribution canvas.
|
|
2019
2417
|
this.shape = GaussianBlurComponent._render1DKernel(GaussianBlurComponent._gen1DKernel(radiusVal));
|
|
2418
|
+
}
|
|
2020
2419
|
this._radiusCache = radiusVal;
|
|
2021
2420
|
_super.prototype.apply.call(this, target, reltime);
|
|
2022
2421
|
};
|
|
@@ -2049,23 +2448,27 @@ var GaussianBlurComponent = /** @class */ (function (_super) {
|
|
|
2049
2448
|
var pascal = GaussianBlurComponent._genPascalRow(2 * radius + 1);
|
|
2050
2449
|
// don't use `reduce` and `map` (overhead?)
|
|
2051
2450
|
var sum = 0;
|
|
2052
|
-
for (var i = 0; i < pascal.length; i++)
|
|
2451
|
+
for (var i = 0; i < pascal.length; i++) {
|
|
2053
2452
|
sum += pascal[i];
|
|
2054
|
-
|
|
2453
|
+
}
|
|
2454
|
+
for (var i = 0; i < pascal.length; i++) {
|
|
2055
2455
|
pascal[i] /= sum;
|
|
2456
|
+
}
|
|
2056
2457
|
return pascal;
|
|
2057
2458
|
};
|
|
2058
2459
|
GaussianBlurComponent._genPascalRow = function (index) {
|
|
2059
|
-
if (index < 0)
|
|
2060
|
-
throw new Error("Invalid index "
|
|
2460
|
+
if (index < 0) {
|
|
2461
|
+
throw new Error("Invalid index ".concat(index));
|
|
2462
|
+
}
|
|
2061
2463
|
var currRow = [1];
|
|
2062
2464
|
for (var i = 1; i < index; i++) {
|
|
2063
2465
|
var nextRow = [];
|
|
2064
2466
|
nextRow.length = currRow.length + 1;
|
|
2065
2467
|
// edges are always 1's
|
|
2066
2468
|
nextRow[0] = nextRow[nextRow.length - 1] = 1;
|
|
2067
|
-
for (var j = 1; j < nextRow.length - 1; j++)
|
|
2469
|
+
for (var j = 1; j < nextRow.length - 1; j++) {
|
|
2068
2470
|
nextRow[j] = currRow[j - 1] + currRow[j];
|
|
2471
|
+
}
|
|
2069
2472
|
currRow = nextRow;
|
|
2070
2473
|
}
|
|
2071
2474
|
return currRow;
|
|
@@ -2136,15 +2539,14 @@ var Pixelate = /** @class */ (function (_super) {
|
|
|
2136
2539
|
pixelSize: '1i'
|
|
2137
2540
|
}
|
|
2138
2541
|
}) || this;
|
|
2139
|
-
/**
|
|
2140
|
-
*/
|
|
2141
2542
|
_this.pixelSize = options.pixelSize || 1;
|
|
2142
2543
|
return _this;
|
|
2143
2544
|
}
|
|
2144
2545
|
Pixelate.prototype.apply = function (target, reltime) {
|
|
2145
2546
|
var ps = val(this, 'pixelSize', reltime);
|
|
2146
|
-
if (ps % 1 !== 0 || ps < 0)
|
|
2547
|
+
if (ps % 1 !== 0 || ps < 0) {
|
|
2147
2548
|
throw new Error('Pixel size must be a nonnegative integer');
|
|
2549
|
+
}
|
|
2148
2550
|
_super.prototype.apply.call(this, target, reltime);
|
|
2149
2551
|
};
|
|
2150
2552
|
return Pixelate;
|
|
@@ -2173,10 +2575,12 @@ var Transform = /** @class */ (function (_super) {
|
|
|
2173
2575
|
return _this;
|
|
2174
2576
|
}
|
|
2175
2577
|
Transform.prototype.apply = function (target, reltime) {
|
|
2176
|
-
if (target.canvas.width !== this._tmpCanvas.width)
|
|
2578
|
+
if (target.canvas.width !== this._tmpCanvas.width) {
|
|
2177
2579
|
this._tmpCanvas.width = target.canvas.width;
|
|
2178
|
-
|
|
2580
|
+
}
|
|
2581
|
+
if (target.canvas.height !== this._tmpCanvas.height) {
|
|
2179
2582
|
this._tmpCanvas.height = target.canvas.height;
|
|
2583
|
+
}
|
|
2180
2584
|
// Use data, since that's the underlying storage
|
|
2181
2585
|
this._tmpMatrix.data = val(this, 'matrix.data', reltime);
|
|
2182
2586
|
this._tmpCtx.setTransform(this._tmpMatrix.a, this._tmpMatrix.b, this._tmpMatrix.c, this._tmpMatrix.d, this._tmpMatrix.e, this._tmpMatrix.f);
|
|
@@ -2202,8 +2606,9 @@ var Transform = /** @class */ (function (_super) {
|
|
|
2202
2606
|
];
|
|
2203
2607
|
}
|
|
2204
2608
|
Matrix.prototype.identity = function () {
|
|
2205
|
-
for (var i = 0; i < this.data.length; i++)
|
|
2609
|
+
for (var i = 0; i < this.data.length; i++) {
|
|
2206
2610
|
this.data[i] = Matrix.IDENTITY.data[i];
|
|
2611
|
+
}
|
|
2207
2612
|
return this;
|
|
2208
2613
|
};
|
|
2209
2614
|
/**
|
|
@@ -2212,8 +2617,9 @@ var Transform = /** @class */ (function (_super) {
|
|
|
2212
2617
|
* @param [val]
|
|
2213
2618
|
*/
|
|
2214
2619
|
Matrix.prototype.cell = function (x, y, val) {
|
|
2215
|
-
if (val !== undefined)
|
|
2620
|
+
if (val !== undefined) {
|
|
2216
2621
|
this.data[3 * y + x] = val;
|
|
2622
|
+
}
|
|
2217
2623
|
return this.data[3 * y + x];
|
|
2218
2624
|
};
|
|
2219
2625
|
Object.defineProperty(Matrix.prototype, "a", {
|
|
@@ -2265,16 +2671,19 @@ var Transform = /** @class */ (function (_super) {
|
|
|
2265
2671
|
*/
|
|
2266
2672
|
Matrix.prototype.multiply = function (other) {
|
|
2267
2673
|
// copy to temporary matrix to avoid modifying `this` while reading from it
|
|
2268
|
-
for (var x = 0; x < 3; x++)
|
|
2674
|
+
for (var x = 0; x < 3; x++) {
|
|
2269
2675
|
for (var y = 0; y < 3; y++) {
|
|
2270
2676
|
var sum = 0;
|
|
2271
|
-
for (var i = 0; i < 3; i++)
|
|
2677
|
+
for (var i = 0; i < 3; i++) {
|
|
2272
2678
|
sum += this.cell(x, i) * other.cell(i, y);
|
|
2679
|
+
}
|
|
2273
2680
|
Matrix._TMP_MATRIX.cell(x, y, sum);
|
|
2274
2681
|
}
|
|
2682
|
+
}
|
|
2275
2683
|
// copy data from TMP_MATRIX to this
|
|
2276
|
-
for (var i = 0; i < Matrix._TMP_MATRIX.data.length; i++)
|
|
2684
|
+
for (var i = 0; i < Matrix._TMP_MATRIX.data.length; i++) {
|
|
2277
2685
|
this.data[i] = Matrix._TMP_MATRIX.data[i];
|
|
2686
|
+
}
|
|
2278
2687
|
return this;
|
|
2279
2688
|
};
|
|
2280
2689
|
/**
|
|
@@ -2329,6 +2738,7 @@ var Transform = /** @class */ (function (_super) {
|
|
|
2329
2738
|
*/
|
|
2330
2739
|
|
|
2331
2740
|
var index$1 = /*#__PURE__*/Object.freeze({
|
|
2741
|
+
__proto__: null,
|
|
2332
2742
|
Base: Base$1,
|
|
2333
2743
|
Brightness: Brightness,
|
|
2334
2744
|
Channels: Channels,
|
|
@@ -2347,6 +2757,52 @@ var index$1 = /*#__PURE__*/Object.freeze({
|
|
|
2347
2757
|
Visual: Visual$1
|
|
2348
2758
|
});
|
|
2349
2759
|
|
|
2760
|
+
var MovieEffectsListener = /** @class */ (function (_super) {
|
|
2761
|
+
__extends(MovieEffectsListener, _super);
|
|
2762
|
+
function MovieEffectsListener(movie) {
|
|
2763
|
+
var _this = _super.call(this) || this;
|
|
2764
|
+
_this._movie = movie;
|
|
2765
|
+
return _this;
|
|
2766
|
+
}
|
|
2767
|
+
MovieEffectsListener.prototype.onAdd = function (effect) {
|
|
2768
|
+
effect.tryAttach(this._movie);
|
|
2769
|
+
};
|
|
2770
|
+
MovieEffectsListener.prototype.onRemove = function (effect) {
|
|
2771
|
+
effect.tryDetach();
|
|
2772
|
+
};
|
|
2773
|
+
return MovieEffectsListener;
|
|
2774
|
+
}(CustomArrayListener));
|
|
2775
|
+
var MovieEffects = /** @class */ (function (_super) {
|
|
2776
|
+
__extends(MovieEffects, _super);
|
|
2777
|
+
function MovieEffects(target, movie) {
|
|
2778
|
+
return _super.call(this, target, new MovieEffectsListener(movie)) || this;
|
|
2779
|
+
}
|
|
2780
|
+
return MovieEffects;
|
|
2781
|
+
}(CustomArray));
|
|
2782
|
+
|
|
2783
|
+
var MovieLayersListener = /** @class */ (function (_super) {
|
|
2784
|
+
__extends(MovieLayersListener, _super);
|
|
2785
|
+
function MovieLayersListener(movie) {
|
|
2786
|
+
var _this = _super.call(this) || this;
|
|
2787
|
+
_this._movie = movie;
|
|
2788
|
+
return _this;
|
|
2789
|
+
}
|
|
2790
|
+
MovieLayersListener.prototype.onAdd = function (layer) {
|
|
2791
|
+
layer.tryAttach(this._movie);
|
|
2792
|
+
};
|
|
2793
|
+
MovieLayersListener.prototype.onRemove = function (layer) {
|
|
2794
|
+
layer.tryDetach();
|
|
2795
|
+
};
|
|
2796
|
+
return MovieLayersListener;
|
|
2797
|
+
}(CustomArrayListener));
|
|
2798
|
+
var MovieLayers = /** @class */ (function (_super) {
|
|
2799
|
+
__extends(MovieLayers, _super);
|
|
2800
|
+
function MovieLayers(target, movie) {
|
|
2801
|
+
return _super.call(this, target, new MovieLayersListener(movie)) || this;
|
|
2802
|
+
}
|
|
2803
|
+
return MovieLayers;
|
|
2804
|
+
}(CustomArray));
|
|
2805
|
+
|
|
2350
2806
|
/**
|
|
2351
2807
|
* @module movie
|
|
2352
2808
|
*/
|
|
@@ -2360,17 +2816,13 @@ var MovieOptions = /** @class */ (function () {
|
|
|
2360
2816
|
*
|
|
2361
2817
|
* Implements a pub/sub system.
|
|
2362
2818
|
*/
|
|
2363
|
-
// TODO: Implement event "durationchange", and more
|
|
2364
|
-
// TODO: Add width and height options
|
|
2365
|
-
// TODO: Make record option to make recording video output to the user while
|
|
2366
|
-
// it's recording
|
|
2367
2819
|
// TODO: rename renderingFrame -> refreshing
|
|
2368
2820
|
var Movie = /** @class */ (function () {
|
|
2369
2821
|
/**
|
|
2370
2822
|
* Creates a new movie.
|
|
2371
2823
|
*/
|
|
2372
2824
|
function Movie(options) {
|
|
2373
|
-
|
|
2825
|
+
this._recording = false;
|
|
2374
2826
|
// Set actx option manually, because it's readonly.
|
|
2375
2827
|
this.actx = options.actx ||
|
|
2376
2828
|
options.audioContext ||
|
|
@@ -2378,236 +2830,294 @@ var Movie = /** @class */ (function () {
|
|
|
2378
2830
|
// eslint-disable-next-line new-cap
|
|
2379
2831
|
new window.webkitAudioContext();
|
|
2380
2832
|
delete options.actx;
|
|
2381
|
-
//
|
|
2382
|
-
|
|
2833
|
+
// Check if required file canvas is provided
|
|
2834
|
+
if (!options.canvas) {
|
|
2835
|
+
throw new Error('Required option "canvas" not provided to Movie');
|
|
2836
|
+
}
|
|
2383
2837
|
// Set canvas option manually, because it's readonly.
|
|
2384
|
-
this._canvas = options.canvas;
|
|
2838
|
+
this._canvas = this._visibleCanvas = options.canvas;
|
|
2385
2839
|
delete options.canvas;
|
|
2386
|
-
// Don't send updates when initializing, so use this instead of newThis:
|
|
2387
2840
|
this._cctx = this.canvas.getContext('2d'); // TODO: make private?
|
|
2841
|
+
// Set options on the movie
|
|
2388
2842
|
applyOptions(options, this);
|
|
2389
|
-
|
|
2390
|
-
this.
|
|
2391
|
-
this.effects = new Proxy(newThis._effectsBack, {
|
|
2392
|
-
deleteProperty: function (target, property) {
|
|
2393
|
-
// Refresh screen when effect is removed, if the movie isn't playing
|
|
2394
|
-
// already.
|
|
2395
|
-
var value = target[property];
|
|
2396
|
-
value.tryDetach();
|
|
2397
|
-
delete target[property];
|
|
2398
|
-
publish(that, 'movie.change.effect.remove', { effect: value });
|
|
2399
|
-
return true;
|
|
2400
|
-
},
|
|
2401
|
-
set: function (target, property, value) {
|
|
2402
|
-
// Check if property is an number (an index)
|
|
2403
|
-
if (!isNaN(Number(property))) {
|
|
2404
|
-
if (target[property]) {
|
|
2405
|
-
publish(that, 'movie.change.effect.remove', {
|
|
2406
|
-
effect: target[property]
|
|
2407
|
-
});
|
|
2408
|
-
target[property].tryDetach();
|
|
2409
|
-
}
|
|
2410
|
-
// Attach effect to movie
|
|
2411
|
-
value.tryAttach(that);
|
|
2412
|
-
target[property] = value;
|
|
2413
|
-
// Refresh screen when effect is set, if the movie isn't playing
|
|
2414
|
-
// already.
|
|
2415
|
-
publish(that, 'movie.change.effect.add', { effect: value });
|
|
2416
|
-
}
|
|
2417
|
-
else {
|
|
2418
|
-
target[property] = value;
|
|
2419
|
-
}
|
|
2420
|
-
return true;
|
|
2421
|
-
}
|
|
2422
|
-
});
|
|
2423
|
-
this._layersBack = [];
|
|
2424
|
-
this.layers = new Proxy(newThis._layersBack, {
|
|
2425
|
-
deleteProperty: function (target, property) {
|
|
2426
|
-
var oldDuration = this.duration;
|
|
2427
|
-
var value = target[property];
|
|
2428
|
-
value.tryDetach(that);
|
|
2429
|
-
delete target[property];
|
|
2430
|
-
var current = that.currentTime >= value.startTime && that.currentTime < value.startTime + value.duration;
|
|
2431
|
-
if (current)
|
|
2432
|
-
publish(that, 'movie.change.layer.remove', { layer: value });
|
|
2433
|
-
publish(that, 'movie.change.duration', { oldDuration: oldDuration });
|
|
2434
|
-
return true;
|
|
2435
|
-
},
|
|
2436
|
-
set: function (target, property, value) {
|
|
2437
|
-
var oldDuration = this.duration;
|
|
2438
|
-
// Check if property is an number (an index)
|
|
2439
|
-
if (!isNaN(Number(property))) {
|
|
2440
|
-
if (target[property]) {
|
|
2441
|
-
publish(that, 'movie.change.layer.remove', {
|
|
2442
|
-
layer: target[property]
|
|
2443
|
-
});
|
|
2444
|
-
target[property].tryDetach();
|
|
2445
|
-
}
|
|
2446
|
-
// Attach layer to movie
|
|
2447
|
-
value.tryAttach(that);
|
|
2448
|
-
target[property] = value;
|
|
2449
|
-
// Refresh screen when a relevant layer is added or removed
|
|
2450
|
-
var current = that.currentTime >= value.startTime && that.currentTime < value.startTime + value.duration;
|
|
2451
|
-
if (current)
|
|
2452
|
-
publish(that, 'movie.change.layer.add', { layer: value });
|
|
2453
|
-
publish(that, 'movie.change.duration', { oldDuration: oldDuration });
|
|
2454
|
-
}
|
|
2455
|
-
else {
|
|
2456
|
-
target[property] = value;
|
|
2457
|
-
}
|
|
2458
|
-
return true;
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2843
|
+
this.effects = new MovieEffects([], this);
|
|
2844
|
+
this.layers = new MovieLayers([], this);
|
|
2461
2845
|
this._paused = true;
|
|
2462
2846
|
this._ended = false;
|
|
2463
|
-
// This
|
|
2464
|
-
//
|
|
2847
|
+
// This lock prevents multiple refresh loops at the same time (see
|
|
2848
|
+
// `render`). It's only valid while rendering.
|
|
2465
2849
|
this._renderingFrame = false;
|
|
2466
2850
|
this.currentTime = 0;
|
|
2467
|
-
//
|
|
2468
|
-
this._mediaRecorder = null;
|
|
2469
|
-
// -1 works well in inequalities
|
|
2470
|
-
// The last time `play` was called
|
|
2851
|
+
// The last time `play` was called, -1 works well in comparisons
|
|
2471
2852
|
this._lastPlayed = -1;
|
|
2472
|
-
// What
|
|
2853
|
+
// What `currentTime` was when `play` was called
|
|
2473
2854
|
this._lastPlayedOffset = -1;
|
|
2474
|
-
// newThis._updateInterval = 0.1; // time in seconds between each "timeupdate" event
|
|
2475
|
-
// newThis._lastUpdate = -1;
|
|
2476
|
-
if (newThis.autoRefresh)
|
|
2477
|
-
newThis.refresh(); // render single frame on creation
|
|
2478
|
-
// Subscribe to own event "change" (child events propogate up)
|
|
2479
|
-
subscribe(newThis, 'movie.change', function () {
|
|
2480
|
-
if (newThis.autoRefresh && !newThis.rendering)
|
|
2481
|
-
newThis.refresh();
|
|
2482
|
-
});
|
|
2483
|
-
// Subscribe to own event "ended"
|
|
2484
|
-
subscribe(newThis, 'movie.recordended', function () {
|
|
2485
|
-
if (newThis.recording) {
|
|
2486
|
-
newThis._mediaRecorder.requestData();
|
|
2487
|
-
newThis._mediaRecorder.stop();
|
|
2488
|
-
}
|
|
2489
|
-
});
|
|
2490
|
-
return newThis;
|
|
2491
2855
|
}
|
|
2856
|
+
Movie.prototype._whenReady = function () {
|
|
2857
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2858
|
+
return __generator(this, function (_a) {
|
|
2859
|
+
switch (_a.label) {
|
|
2860
|
+
case 0: return [4 /*yield*/, Promise.all([
|
|
2861
|
+
Promise.all(this.layers.map(function (layer) { return layer.whenReady(); })),
|
|
2862
|
+
Promise.all(this.effects.map(function (effect) { return effect.whenReady(); }))
|
|
2863
|
+
])];
|
|
2864
|
+
case 1:
|
|
2865
|
+
_a.sent();
|
|
2866
|
+
return [2 /*return*/];
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
});
|
|
2870
|
+
};
|
|
2492
2871
|
/**
|
|
2493
2872
|
* Plays the movie
|
|
2494
|
-
*
|
|
2873
|
+
*
|
|
2874
|
+
* @param [options]
|
|
2875
|
+
* @param [options.onStart] Called when the movie starts playing
|
|
2876
|
+
*
|
|
2877
|
+
* @return Fulfilled when the movie is done playing, never fails
|
|
2495
2878
|
*/
|
|
2496
|
-
Movie.prototype.play = function () {
|
|
2497
|
-
var
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2879
|
+
Movie.prototype.play = function (options) {
|
|
2880
|
+
var _a;
|
|
2881
|
+
if (options === void 0) { options = {}; }
|
|
2882
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2883
|
+
var _this = this;
|
|
2884
|
+
return __generator(this, function (_b) {
|
|
2885
|
+
switch (_b.label) {
|
|
2886
|
+
case 0: return [4 /*yield*/, this._whenReady()];
|
|
2887
|
+
case 1:
|
|
2888
|
+
_b.sent();
|
|
2889
|
+
if (!this.paused) {
|
|
2890
|
+
throw new Error('Already playing');
|
|
2891
|
+
}
|
|
2892
|
+
this._paused = this._ended = false;
|
|
2893
|
+
this._lastPlayed = performance.now();
|
|
2894
|
+
this._lastPlayedOffset = this.currentTime;
|
|
2895
|
+
(_a = options.onStart) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
2896
|
+
// For backwards compatibility
|
|
2897
|
+
publish(this, 'movie.play', {});
|
|
2898
|
+
// Repeatedly render frames until the movie ends
|
|
2899
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
2900
|
+
if (!_this.renderingFrame) {
|
|
2901
|
+
// Not rendering (and not playing), so play.
|
|
2902
|
+
_this._render(true, undefined, resolve);
|
|
2903
|
+
}
|
|
2904
|
+
// Stop rendering frame if currently doing so, because playing has higher
|
|
2905
|
+
// priority. This will affect the next _render call.
|
|
2906
|
+
_this._renderingFrame = false;
|
|
2907
|
+
})];
|
|
2908
|
+
case 2:
|
|
2909
|
+
// Repeatedly render frames until the movie ends
|
|
2910
|
+
_b.sent();
|
|
2911
|
+
return [2 /*return*/];
|
|
2912
|
+
}
|
|
2913
|
+
});
|
|
2914
|
+
});
|
|
2915
|
+
};
|
|
2916
|
+
/**
|
|
2917
|
+
* Updates the rendering canvas and audio destination to the visible canvas
|
|
2918
|
+
* and the audio context destination.
|
|
2919
|
+
*/
|
|
2920
|
+
Movie.prototype._show = function () {
|
|
2921
|
+
this._canvas = this._visibleCanvas;
|
|
2922
|
+
this._cctx = this.canvas.getContext('2d');
|
|
2923
|
+
publish(this, 'audiodestinationupdate', { movie: this, destination: this.actx.destination });
|
|
2924
|
+
};
|
|
2925
|
+
/**
|
|
2926
|
+
* Streams the movie to a MediaStream
|
|
2927
|
+
*
|
|
2928
|
+
* @param options Options for the stream
|
|
2929
|
+
* @param options.frameRate The frame rate of the stream's video
|
|
2930
|
+
* @param options.duration The duration of the stream in seconds
|
|
2931
|
+
* @param options.video Whether to stream video. Defaults to true.
|
|
2932
|
+
* @param options.audio Whether to stream audio. Defaults to true.
|
|
2933
|
+
* @param options.onStart Called when the stream is started
|
|
2934
|
+
* @return Fulfilled when the stream is done, never fails
|
|
2935
|
+
*/
|
|
2936
|
+
Movie.prototype.stream = function (options) {
|
|
2937
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2938
|
+
var tracks, visualStream, hasMediaTracks, audioDestination, audioStream;
|
|
2939
|
+
var _this = this;
|
|
2940
|
+
return __generator(this, function (_a) {
|
|
2941
|
+
switch (_a.label) {
|
|
2942
|
+
case 0:
|
|
2943
|
+
// Validate options
|
|
2944
|
+
if (!options || !options.frameRate) {
|
|
2945
|
+
throw new Error('Required option "frameRate" not provided to Movie.stream');
|
|
2946
|
+
}
|
|
2947
|
+
if (options.video === false && options.audio === false) {
|
|
2948
|
+
throw new Error('Both video and audio cannot be disabled');
|
|
2949
|
+
}
|
|
2950
|
+
if (!this.paused) {
|
|
2951
|
+
throw new Error("Cannot stream movie while it's already playing");
|
|
2952
|
+
}
|
|
2953
|
+
// Wait until all resources are loaded
|
|
2954
|
+
return [4 /*yield*/, this._whenReady()
|
|
2955
|
+
// Create a temporary canvas to stream from
|
|
2956
|
+
];
|
|
2957
|
+
case 1:
|
|
2958
|
+
// Wait until all resources are loaded
|
|
2959
|
+
_a.sent();
|
|
2960
|
+
// Create a temporary canvas to stream from
|
|
2961
|
+
this._canvas = document.createElement('canvas');
|
|
2962
|
+
this.canvas.width = this._visibleCanvas.width;
|
|
2963
|
+
this.canvas.height = this._visibleCanvas.height;
|
|
2964
|
+
this._cctx = this.canvas.getContext('2d');
|
|
2965
|
+
tracks = [];
|
|
2966
|
+
// Add video track
|
|
2967
|
+
if (options.video !== false) {
|
|
2968
|
+
visualStream = this.canvas.captureStream(options.frameRate);
|
|
2969
|
+
tracks = tracks.concat(visualStream.getTracks());
|
|
2970
|
+
}
|
|
2971
|
+
hasMediaTracks = this.layers.some(function (layer) { return layer instanceof Audio || layer instanceof Video; });
|
|
2972
|
+
// If no media tracks present, don't include an audio stream, because
|
|
2973
|
+
// Chrome doesn't record silence when an audio stream is present.
|
|
2974
|
+
if (hasMediaTracks && options.audio !== false) {
|
|
2975
|
+
audioDestination = this.actx.createMediaStreamDestination();
|
|
2976
|
+
audioStream = audioDestination.stream;
|
|
2977
|
+
tracks = tracks.concat(audioStream.getTracks());
|
|
2978
|
+
// Notify layers and any other listeners of the new audio destination
|
|
2979
|
+
publish(this, 'audiodestinationupdate', { movie: this, destination: audioDestination });
|
|
2980
|
+
}
|
|
2981
|
+
// Create the stream
|
|
2982
|
+
this._currentStream = new MediaStream(tracks);
|
|
2983
|
+
// Play the movie
|
|
2984
|
+
this._endTime = options.duration ? this.currentTime + options.duration : this.duration;
|
|
2985
|
+
return [4 /*yield*/, this.play({
|
|
2986
|
+
onStart: function () {
|
|
2987
|
+
// Call the user's onStart callback
|
|
2988
|
+
options.onStart(_this._currentStream);
|
|
2989
|
+
}
|
|
2990
|
+
})
|
|
2991
|
+
// Clear the stream after the movie is done playing
|
|
2992
|
+
];
|
|
2993
|
+
case 2:
|
|
2994
|
+
_a.sent();
|
|
2995
|
+
// Clear the stream after the movie is done playing
|
|
2996
|
+
this._currentStream.getTracks().forEach(function (track) {
|
|
2997
|
+
track.stop();
|
|
2998
|
+
});
|
|
2999
|
+
this._currentStream = null;
|
|
3000
|
+
this._show();
|
|
3001
|
+
return [2 /*return*/];
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
2511
3004
|
});
|
|
2512
3005
|
};
|
|
2513
3006
|
/**
|
|
2514
3007
|
* Plays the movie in the background and records it
|
|
2515
3008
|
*
|
|
2516
3009
|
* @param options
|
|
2517
|
-
* @param frameRate
|
|
3010
|
+
* @param [options.frameRate] - Video frame rate
|
|
2518
3011
|
* @param [options.video=true] - whether to include video in recording
|
|
2519
3012
|
* @param [options.audio=true] - whether to include audio in recording
|
|
2520
|
-
* @param [options.mediaRecorderOptions=undefined] -
|
|
3013
|
+
* @param [options.mediaRecorderOptions=undefined] - Options to pass to the
|
|
3014
|
+
* `MediaRecorder` constructor
|
|
2521
3015
|
* @param [options.type='video/webm'] - MIME type for exported video
|
|
2522
|
-
*
|
|
2523
|
-
* @return
|
|
3016
|
+
* @param [options.onStart] - Called when the recording starts
|
|
3017
|
+
* @return Resolves when done recording, rejects when media recorder errors
|
|
2524
3018
|
*/
|
|
2525
|
-
//
|
|
2526
|
-
// TODO: figure out how to do offline recording (faster than realtime).
|
|
2527
|
-
// TODO: improve recording performance to increase frame rate?
|
|
3019
|
+
// TODO: Improve recording performance to increase frame rate
|
|
2528
3020
|
Movie.prototype.record = function (options) {
|
|
2529
|
-
var
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
3021
|
+
var _a;
|
|
3022
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
3023
|
+
var mimeType, stream, recordedChunks, mediaRecorderOptions;
|
|
3024
|
+
var _this = this;
|
|
3025
|
+
return __generator(this, function (_b) {
|
|
3026
|
+
switch (_b.label) {
|
|
3027
|
+
case 0:
|
|
3028
|
+
// Validate options
|
|
3029
|
+
if (options.video === false && options.audio === false) {
|
|
3030
|
+
throw new Error('Both video and audio cannot be disabled');
|
|
3031
|
+
}
|
|
3032
|
+
if (!this.paused) {
|
|
3033
|
+
throw new Error("Cannot record movie while it's already playing");
|
|
3034
|
+
}
|
|
3035
|
+
mimeType = options.type || 'video/webm';
|
|
3036
|
+
if (MediaRecorder && MediaRecorder.isTypeSupported && !MediaRecorder.isTypeSupported(mimeType)) {
|
|
3037
|
+
throw new Error('Please pass a valid MIME type for the exported video');
|
|
3038
|
+
}
|
|
3039
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
3040
|
+
_this.stream({
|
|
3041
|
+
frameRate: options.frameRate,
|
|
3042
|
+
duration: options.duration,
|
|
3043
|
+
video: options.video,
|
|
3044
|
+
audio: options.audio,
|
|
3045
|
+
onStart: resolve
|
|
3046
|
+
}).then(function () {
|
|
3047
|
+
// Stop the media recorder when the movie is done playing
|
|
3048
|
+
_this._recorder.requestData();
|
|
3049
|
+
_this._recorder.stop();
|
|
3050
|
+
});
|
|
3051
|
+
})
|
|
3052
|
+
// The array to store the recorded chunks
|
|
3053
|
+
];
|
|
3054
|
+
case 1:
|
|
3055
|
+
stream = _b.sent();
|
|
3056
|
+
recordedChunks = [];
|
|
3057
|
+
mediaRecorderOptions = __assign(__assign({}, (options.mediaRecorderOptions || {})), { mimeType: mimeType });
|
|
3058
|
+
this._recorder = new MediaRecorder(stream, mediaRecorderOptions);
|
|
3059
|
+
this._recorder.ondataavailable = function (event) {
|
|
3060
|
+
// if (this._paused) reject(new Error("Recording was interrupted"));
|
|
3061
|
+
if (event.data.size > 0) {
|
|
3062
|
+
recordedChunks.push(event.data);
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
// Start recording
|
|
3066
|
+
this._recorder.start();
|
|
3067
|
+
this._recording = true;
|
|
3068
|
+
// Notify caller that the media recorder has started
|
|
3069
|
+
(_a = options.onStart) === null || _a === void 0 ? void 0 : _a.call(options, this._recorder);
|
|
3070
|
+
// For backwards compatibility
|
|
3071
|
+
publish(this, 'movie.record', { options: options });
|
|
3072
|
+
// Wait until the media recorder is done recording and processing
|
|
3073
|
+
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
3074
|
+
_this._recorder.onstop = function () {
|
|
3075
|
+
resolve();
|
|
3076
|
+
};
|
|
3077
|
+
_this._recorder.onerror = reject;
|
|
3078
|
+
})
|
|
3079
|
+
// Clean up
|
|
3080
|
+
];
|
|
3081
|
+
case 2:
|
|
3082
|
+
// Wait until the media recorder is done recording and processing
|
|
3083
|
+
_b.sent();
|
|
3084
|
+
// Clean up
|
|
3085
|
+
this._paused = true;
|
|
3086
|
+
this._ended = true;
|
|
3087
|
+
this._recording = false;
|
|
3088
|
+
// Construct the exported video out of all the frame blobs.
|
|
3089
|
+
return [2 /*return*/, new Blob(recordedChunks, {
|
|
3090
|
+
type: mimeType
|
|
3091
|
+
})];
|
|
3092
|
+
}
|
|
3093
|
+
});
|
|
2590
3094
|
});
|
|
2591
3095
|
};
|
|
2592
3096
|
/**
|
|
2593
|
-
* Stops the movie
|
|
2594
|
-
* @return
|
|
3097
|
+
* Stops the movie without resetting the playback position
|
|
3098
|
+
* @return The movie
|
|
2595
3099
|
*/
|
|
2596
3100
|
Movie.prototype.pause = function () {
|
|
3101
|
+
// Update state
|
|
2597
3102
|
this._paused = true;
|
|
2598
3103
|
// Deactivate all layers
|
|
2599
|
-
for (var i = 0; i < this.layers.length; i++)
|
|
3104
|
+
for (var i = 0; i < this.layers.length; i++) {
|
|
2600
3105
|
if (Object.prototype.hasOwnProperty.call(this.layers, i)) {
|
|
2601
3106
|
var layer = this.layers[i];
|
|
2602
|
-
layer.
|
|
2603
|
-
|
|
3107
|
+
if (layer.active) {
|
|
3108
|
+
layer.stop();
|
|
3109
|
+
layer.active = false;
|
|
3110
|
+
}
|
|
2604
3111
|
}
|
|
3112
|
+
}
|
|
3113
|
+
// For backwards compatibility, notify event listeners that the movie has
|
|
3114
|
+
// paused
|
|
2605
3115
|
publish(this, 'movie.pause', {});
|
|
2606
3116
|
return this;
|
|
2607
3117
|
};
|
|
2608
3118
|
/**
|
|
2609
3119
|
* Stops playback and resets the playback position
|
|
2610
|
-
* @return
|
|
3120
|
+
* @return The movie
|
|
2611
3121
|
*/
|
|
2612
3122
|
Movie.prototype.stop = function () {
|
|
2613
3123
|
this.pause();
|
|
@@ -2616,8 +3126,8 @@ var Movie = /** @class */ (function () {
|
|
|
2616
3126
|
};
|
|
2617
3127
|
/**
|
|
2618
3128
|
* @param [timestamp=performance.now()]
|
|
2619
|
-
* @param [done=undefined] -
|
|
2620
|
-
*
|
|
3129
|
+
* @param [done=undefined] - Called when done playing or when the current
|
|
3130
|
+
* frame is loaded
|
|
2621
3131
|
*/
|
|
2622
3132
|
Movie.prototype._render = function (repeat, timestamp, done) {
|
|
2623
3133
|
var _this = this;
|
|
@@ -2625,177 +3135,207 @@ var Movie = /** @class */ (function () {
|
|
|
2625
3135
|
if (done === void 0) { done = undefined; }
|
|
2626
3136
|
clearCachedValues(this);
|
|
2627
3137
|
if (!this.rendering) {
|
|
2628
|
-
// (
|
|
3138
|
+
// (this.paused && !this._renderingFrame) is true so it's playing or it's
|
|
2629
3139
|
// rendering a single frame.
|
|
2630
|
-
if (done)
|
|
3140
|
+
if (done) {
|
|
2631
3141
|
done();
|
|
3142
|
+
}
|
|
2632
3143
|
return;
|
|
2633
3144
|
}
|
|
2634
|
-
this.
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
// TODO:
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
3145
|
+
if (this.ready) {
|
|
3146
|
+
publish(this, 'movie.loadeddata', { movie: this });
|
|
3147
|
+
// If the movie is streaming or recording, resume the media recorder
|
|
3148
|
+
if (this._recording && this._recorder.state === 'paused') {
|
|
3149
|
+
this._recorder.resume();
|
|
3150
|
+
}
|
|
3151
|
+
// If the movie is streaming or recording, end at the specified duration.
|
|
3152
|
+
// Otherwise, end at the movie's duration, because play() does not
|
|
3153
|
+
// support playing a portion of the movie yet.
|
|
3154
|
+
// TODO: Is calling duration every frame bad for performance? (remember,
|
|
3155
|
+
// it's calling Array.reduce)
|
|
3156
|
+
var end = this._currentStream ? this._endTime : this.duration;
|
|
3157
|
+
this._updateCurrentTime(timestamp, end);
|
|
3158
|
+
if (this.currentTime === end) {
|
|
3159
|
+
if (this.recording) {
|
|
3160
|
+
publish(this, 'movie.recordended', { movie: this });
|
|
3161
|
+
}
|
|
3162
|
+
if (this.currentTime === this.duration) {
|
|
3163
|
+
publish(this, 'movie.ended', { movie: this, repeat: this.repeat });
|
|
3164
|
+
}
|
|
3165
|
+
// Don't use setter, which publishes 'seek'. Instead, update the
|
|
3166
|
+
// value and publish a 'imeupdate' event.
|
|
2647
3167
|
this._currentTime = 0;
|
|
2648
3168
|
publish(this, 'movie.timeupdate', { movie: this });
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
3169
|
+
this._lastPlayed = performance.now();
|
|
3170
|
+
this._lastPlayedOffset = 0; // this.currentTime
|
|
3171
|
+
this._renderingFrame = false;
|
|
3172
|
+
// Stop playback or recording if done (except if it's playing and repeat
|
|
3173
|
+
// is true)
|
|
3174
|
+
if (!(!this.recording && this.repeat)) {
|
|
3175
|
+
this._paused = true;
|
|
3176
|
+
this._ended = true;
|
|
3177
|
+
// Deactivate all layers
|
|
3178
|
+
for (var i = 0; i < this.layers.length; i++) {
|
|
3179
|
+
if (Object.prototype.hasOwnProperty.call(this.layers, i)) {
|
|
3180
|
+
var layer = this.layers[i];
|
|
3181
|
+
// A layer that has been deleted before layers.length has been updated
|
|
3182
|
+
// (see the layers proxy in the constructor).
|
|
3183
|
+
if (!layer || !layer.active) {
|
|
3184
|
+
continue;
|
|
3185
|
+
}
|
|
3186
|
+
layer.stop();
|
|
3187
|
+
layer.active = false;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
publish(this, 'movie.pause', {});
|
|
3191
|
+
if (done) {
|
|
3192
|
+
done();
|
|
2668
3193
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
return;
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
2672
3196
|
}
|
|
3197
|
+
// Do render
|
|
3198
|
+
this._renderBackground(timestamp);
|
|
3199
|
+
this._renderLayers();
|
|
3200
|
+
this._applyEffects();
|
|
2673
3201
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
//
|
|
2682
|
-
//
|
|
2683
|
-
//
|
|
2684
|
-
|
|
3202
|
+
else {
|
|
3203
|
+
// If we are recording, pause the media recorder until the movie is
|
|
3204
|
+
// ready.
|
|
3205
|
+
if (this.recording && this._recorder.state === 'recording') {
|
|
3206
|
+
this._recorder.pause();
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
// If the frame didn't load this instant, repeatedly frame-render until it
|
|
3210
|
+
// is loaded.
|
|
3211
|
+
// If the expression below is true, don't publish an event, just silently
|
|
3212
|
+
// stop the render loop.
|
|
3213
|
+
if (this._renderingFrame && this.ready) {
|
|
2685
3214
|
this._renderingFrame = false;
|
|
2686
|
-
if (done)
|
|
3215
|
+
if (done) {
|
|
2687
3216
|
done();
|
|
3217
|
+
}
|
|
2688
3218
|
return;
|
|
2689
3219
|
}
|
|
3220
|
+
// TODO: Is making a new arrow function every frame bad for performance?
|
|
2690
3221
|
window.requestAnimationFrame(function () {
|
|
2691
3222
|
_this._render(repeat, undefined, done);
|
|
2692
|
-
});
|
|
3223
|
+
});
|
|
2693
3224
|
};
|
|
2694
|
-
Movie.prototype._updateCurrentTime = function (
|
|
2695
|
-
// If we're only
|
|
2696
|
-
//
|
|
3225
|
+
Movie.prototype._updateCurrentTime = function (timestampMs, end) {
|
|
3226
|
+
// If we're only frame-rendering (current frame only), it doesn't matter if
|
|
3227
|
+
// it's paused or not.
|
|
2697
3228
|
if (!this._renderingFrame) {
|
|
2698
|
-
|
|
2699
|
-
var
|
|
2700
|
-
var currentTime = this._lastPlayedOffset + sinceLastPlayed; // don't use setter
|
|
3229
|
+
var sinceLastPlayed = (timestampMs - this._lastPlayed) / 1000;
|
|
3230
|
+
var currentTime = this._lastPlayedOffset + sinceLastPlayed;
|
|
2701
3231
|
if (this.currentTime !== currentTime) {
|
|
3232
|
+
// Update the current time (don't use setter)
|
|
2702
3233
|
this._currentTime = currentTime;
|
|
3234
|
+
// For backwards compatibility, publish a 'movie.timeupdate' event.
|
|
2703
3235
|
publish(this, 'movie.timeupdate', { movie: this });
|
|
2704
3236
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
3237
|
+
if (this.currentTime > end) {
|
|
3238
|
+
this._currentTime = end;
|
|
3239
|
+
}
|
|
2707
3240
|
}
|
|
2708
3241
|
};
|
|
2709
3242
|
Movie.prototype._renderBackground = function (timestamp) {
|
|
2710
3243
|
this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
3244
|
+
// Evaluate background color (since it's a dynamic property)
|
|
2711
3245
|
var background = val(this, 'background', timestamp);
|
|
2712
|
-
if (background) {
|
|
3246
|
+
if (background) {
|
|
2713
3247
|
this.cctx.fillStyle = background;
|
|
2714
3248
|
this.cctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
2715
3249
|
}
|
|
2716
3250
|
};
|
|
2717
3251
|
/**
|
|
2718
|
-
* @return whether or not video frames are loaded
|
|
2719
3252
|
* @param [timestamp=performance.now()]
|
|
2720
|
-
* @private
|
|
2721
3253
|
*/
|
|
2722
3254
|
Movie.prototype._renderLayers = function () {
|
|
2723
|
-
var frameFullyLoaded = true;
|
|
2724
3255
|
for (var i = 0; i < this.layers.length; i++) {
|
|
2725
|
-
if (!Object.prototype.hasOwnProperty.call(this.layers, i))
|
|
3256
|
+
if (!Object.prototype.hasOwnProperty.call(this.layers, i)) {
|
|
2726
3257
|
continue;
|
|
3258
|
+
}
|
|
2727
3259
|
var layer = this.layers[i];
|
|
2728
3260
|
// A layer that has been deleted before layers.length has been updated
|
|
2729
3261
|
// (see the layers proxy in the constructor).
|
|
2730
|
-
if (!layer)
|
|
3262
|
+
if (!layer) {
|
|
2731
3263
|
continue;
|
|
2732
|
-
|
|
3264
|
+
}
|
|
2733
3265
|
// Cancel operation if layer disabled or outside layer time interval
|
|
3266
|
+
var reltime = this.currentTime - layer.startTime;
|
|
2734
3267
|
if (!val(layer, 'enabled', reltime) ||
|
|
2735
3268
|
// TODO > or >= ?
|
|
2736
3269
|
this.currentTime < layer.startTime || this.currentTime > layer.startTime + layer.duration) {
|
|
2737
3270
|
// Layer is not active.
|
|
2738
3271
|
// If only rendering this frame, we are not "starting" the layer.
|
|
2739
3272
|
if (layer.active && !this._renderingFrame) {
|
|
2740
|
-
// TODO: make a `deactivate()` method?
|
|
2741
3273
|
layer.stop();
|
|
2742
3274
|
layer.active = false;
|
|
2743
3275
|
}
|
|
2744
3276
|
continue;
|
|
2745
3277
|
}
|
|
3278
|
+
// If we are playing (not refreshing), update the layer's progress
|
|
3279
|
+
if (!this._renderingFrame) {
|
|
3280
|
+
layer.progress(reltime);
|
|
3281
|
+
}
|
|
2746
3282
|
// If only rendering this frame, we are not "starting" the layer
|
|
2747
3283
|
if (!layer.active && val(layer, 'enabled', reltime) && !this._renderingFrame) {
|
|
2748
|
-
// TODO: make an `activate()` method?
|
|
2749
3284
|
layer.start();
|
|
2750
3285
|
layer.active = true;
|
|
2751
3286
|
}
|
|
2752
|
-
// if the layer has an input file
|
|
2753
|
-
if ('source' in layer)
|
|
2754
|
-
frameFullyLoaded = frameFullyLoaded && layer.source.readyState >= 2;
|
|
2755
3287
|
layer.render();
|
|
2756
3288
|
// if the layer has visual component
|
|
2757
3289
|
if (layer instanceof Visual) {
|
|
2758
3290
|
var canvas = layer.canvas;
|
|
2759
|
-
|
|
2760
|
-
// if the layer has an area (else InvalidStateError from canvas)
|
|
2761
|
-
if (canvas.width * canvas.height > 0)
|
|
3291
|
+
if (canvas.width * canvas.height > 0) {
|
|
2762
3292
|
this.cctx.drawImage(canvas, val(layer, 'x', reltime), val(layer, 'y', reltime), canvas.width, canvas.height);
|
|
3293
|
+
}
|
|
2763
3294
|
}
|
|
2764
3295
|
}
|
|
2765
|
-
return frameFullyLoaded;
|
|
2766
3296
|
};
|
|
2767
3297
|
Movie.prototype._applyEffects = function () {
|
|
2768
3298
|
for (var i = 0; i < this.effects.length; i++) {
|
|
2769
3299
|
var effect = this.effects[i];
|
|
2770
3300
|
// An effect that has been deleted before effects.length has been updated
|
|
2771
3301
|
// (see the effectsproxy in the constructor).
|
|
2772
|
-
if (!effect)
|
|
3302
|
+
if (!effect) {
|
|
2773
3303
|
continue;
|
|
3304
|
+
}
|
|
2774
3305
|
effect.apply(this, this.currentTime);
|
|
2775
3306
|
}
|
|
2776
3307
|
};
|
|
2777
3308
|
/**
|
|
2778
|
-
* Refreshes the screen
|
|
2779
|
-
*
|
|
3309
|
+
* Refreshes the screen
|
|
3310
|
+
*
|
|
3311
|
+
* Only use this if auto-refresh is disabled
|
|
3312
|
+
*
|
|
3313
|
+
* @return - Promise that resolves when the frame is loaded
|
|
2780
3314
|
*/
|
|
2781
3315
|
Movie.prototype.refresh = function () {
|
|
2782
3316
|
var _this = this;
|
|
3317
|
+
// Refreshing while playing can interrupt playback
|
|
3318
|
+
if (!this.paused) {
|
|
3319
|
+
throw new Error('Already playing');
|
|
3320
|
+
}
|
|
2783
3321
|
return new Promise(function (resolve) {
|
|
2784
3322
|
_this._renderingFrame = true;
|
|
2785
3323
|
_this._render(false, undefined, resolve);
|
|
2786
3324
|
});
|
|
2787
3325
|
};
|
|
2788
3326
|
/**
|
|
2789
|
-
* Convienence method
|
|
3327
|
+
* Convienence method (TODO: remove)
|
|
2790
3328
|
*/
|
|
2791
3329
|
Movie.prototype._publishToLayers = function (type, event) {
|
|
2792
|
-
for (var i = 0; i < this.layers.length; i++)
|
|
2793
|
-
if (Object.prototype.hasOwnProperty.call(this.layers, i))
|
|
3330
|
+
for (var i = 0; i < this.layers.length; i++) {
|
|
3331
|
+
if (Object.prototype.hasOwnProperty.call(this.layers, i)) {
|
|
2794
3332
|
publish(this.layers[i], type, event);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
2795
3335
|
};
|
|
2796
3336
|
Object.defineProperty(Movie.prototype, "rendering", {
|
|
2797
3337
|
/**
|
|
2798
|
-
*
|
|
3338
|
+
* `true` if the movie is playing, recording or refreshing
|
|
2799
3339
|
*/
|
|
2800
3340
|
get: function () {
|
|
2801
3341
|
return !this.paused || this._renderingFrame;
|
|
@@ -2805,7 +3345,7 @@ var Movie = /** @class */ (function () {
|
|
|
2805
3345
|
});
|
|
2806
3346
|
Object.defineProperty(Movie.prototype, "renderingFrame", {
|
|
2807
3347
|
/**
|
|
2808
|
-
*
|
|
3348
|
+
* `true` if the movie is refreshing the current frame
|
|
2809
3349
|
*/
|
|
2810
3350
|
get: function () {
|
|
2811
3351
|
return this._renderingFrame;
|
|
@@ -2815,17 +3355,19 @@ var Movie = /** @class */ (function () {
|
|
|
2815
3355
|
});
|
|
2816
3356
|
Object.defineProperty(Movie.prototype, "recording", {
|
|
2817
3357
|
/**
|
|
2818
|
-
*
|
|
3358
|
+
* `true` if the movie is recording
|
|
2819
3359
|
*/
|
|
2820
3360
|
get: function () {
|
|
2821
|
-
return
|
|
3361
|
+
return this._recording;
|
|
2822
3362
|
},
|
|
2823
3363
|
enumerable: false,
|
|
2824
3364
|
configurable: true
|
|
2825
3365
|
});
|
|
2826
3366
|
Object.defineProperty(Movie.prototype, "duration", {
|
|
2827
3367
|
/**
|
|
2828
|
-
* The
|
|
3368
|
+
* The duration of the movie in seconds
|
|
3369
|
+
*
|
|
3370
|
+
* Calculated from the end time of the last layer
|
|
2829
3371
|
*/
|
|
2830
3372
|
// TODO: dirty flag?
|
|
2831
3373
|
get: function () {
|
|
@@ -2835,16 +3377,16 @@ var Movie = /** @class */ (function () {
|
|
|
2835
3377
|
configurable: true
|
|
2836
3378
|
});
|
|
2837
3379
|
/**
|
|
2838
|
-
*
|
|
3380
|
+
* Convenience method for `layers.push()`
|
|
2839
3381
|
* @param layer
|
|
2840
|
-
* @return
|
|
3382
|
+
* @return The movie
|
|
2841
3383
|
*/
|
|
2842
3384
|
Movie.prototype.addLayer = function (layer) {
|
|
2843
3385
|
this.layers.push(layer);
|
|
2844
3386
|
return this;
|
|
2845
3387
|
};
|
|
2846
3388
|
/**
|
|
2847
|
-
*
|
|
3389
|
+
* Convenience method for `effects.push()`
|
|
2848
3390
|
* @param effect
|
|
2849
3391
|
* @return the movie
|
|
2850
3392
|
*/
|
|
@@ -2854,6 +3396,7 @@ var Movie = /** @class */ (function () {
|
|
|
2854
3396
|
};
|
|
2855
3397
|
Object.defineProperty(Movie.prototype, "paused", {
|
|
2856
3398
|
/**
|
|
3399
|
+
* `true` if the movie is paused
|
|
2857
3400
|
*/
|
|
2858
3401
|
get: function () {
|
|
2859
3402
|
return this._paused;
|
|
@@ -2863,7 +3406,7 @@ var Movie = /** @class */ (function () {
|
|
|
2863
3406
|
});
|
|
2864
3407
|
Object.defineProperty(Movie.prototype, "ended", {
|
|
2865
3408
|
/**
|
|
2866
|
-
*
|
|
3409
|
+
* `true` if the playback position is at the end of the movie
|
|
2867
3410
|
*/
|
|
2868
3411
|
get: function () {
|
|
2869
3412
|
return this._ended;
|
|
@@ -2871,50 +3414,89 @@ var Movie = /** @class */ (function () {
|
|
|
2871
3414
|
enumerable: false,
|
|
2872
3415
|
configurable: true
|
|
2873
3416
|
});
|
|
3417
|
+
/**
|
|
3418
|
+
* Skips to the provided playback position, updating {@link currentTime}.
|
|
3419
|
+
*
|
|
3420
|
+
* @param time - The new playback position (in seconds)
|
|
3421
|
+
*/
|
|
3422
|
+
Movie.prototype.seek = function (time) {
|
|
3423
|
+
this._currentTime = time;
|
|
3424
|
+
// Call `seek` on every layer
|
|
3425
|
+
for (var i = 0; i < this.layers.length; i++) {
|
|
3426
|
+
var layer = this.layers[i];
|
|
3427
|
+
if (layer) {
|
|
3428
|
+
var relativeTime = time - layer.startTime;
|
|
3429
|
+
if (relativeTime >= 0 && relativeTime <= layer.duration) {
|
|
3430
|
+
layer.seek(relativeTime);
|
|
3431
|
+
}
|
|
3432
|
+
else {
|
|
3433
|
+
layer.seek(undefined);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
// For backwards compatibility, publish a `seek` event
|
|
3438
|
+
publish(this, 'movie.seek', {});
|
|
3439
|
+
};
|
|
2874
3440
|
Object.defineProperty(Movie.prototype, "currentTime", {
|
|
2875
3441
|
/**
|
|
2876
|
-
* The current playback position
|
|
3442
|
+
* The current playback position in seconds
|
|
2877
3443
|
*/
|
|
2878
3444
|
get: function () {
|
|
2879
3445
|
return this._currentTime;
|
|
2880
3446
|
},
|
|
3447
|
+
/**
|
|
3448
|
+
* Skips to the provided playback position, updating {@link currentTime}.
|
|
3449
|
+
*
|
|
3450
|
+
* @param time - The new playback position (in seconds)
|
|
3451
|
+
*
|
|
3452
|
+
* @deprecated Use `seek` instead
|
|
3453
|
+
*/
|
|
2881
3454
|
set: function (time) {
|
|
2882
|
-
this.
|
|
2883
|
-
publish(this, 'movie.seek', {});
|
|
2884
|
-
// Render single frame to match new time
|
|
2885
|
-
if (this.autoRefresh)
|
|
2886
|
-
this.refresh();
|
|
3455
|
+
this.seek(time);
|
|
2887
3456
|
},
|
|
2888
3457
|
enumerable: false,
|
|
2889
3458
|
configurable: true
|
|
2890
3459
|
});
|
|
2891
3460
|
/**
|
|
2892
|
-
*
|
|
2893
|
-
* `set currentTime`.
|
|
3461
|
+
* Skips to the provided playback position, updating {@link currentTime}.
|
|
2894
3462
|
*
|
|
2895
|
-
* @param time -
|
|
2896
|
-
* @param [refresh=true] -
|
|
2897
|
-
* @return resolves when the current frame is rendered if
|
|
2898
|
-
*
|
|
3463
|
+
* @param time - The new time (in seconds)
|
|
3464
|
+
* @param [refresh=true] - Render a single frame?
|
|
3465
|
+
* @return Promise that resolves when the current frame is rendered if
|
|
3466
|
+
* `refresh` is true; otherwise resolves immediately.
|
|
2899
3467
|
*
|
|
3468
|
+
* @deprecated Call {@link seek} and {@link refresh} separately
|
|
2900
3469
|
*/
|
|
2901
|
-
// TODO: Refresh if
|
|
3470
|
+
// TODO: Refresh only if auto-refreshing is enabled
|
|
2902
3471
|
Movie.prototype.setCurrentTime = function (time, refresh) {
|
|
2903
3472
|
var _this = this;
|
|
2904
3473
|
if (refresh === void 0) { refresh = true; }
|
|
2905
3474
|
return new Promise(function (resolve, reject) {
|
|
2906
|
-
_this.
|
|
2907
|
-
|
|
2908
|
-
if (refresh)
|
|
3475
|
+
_this.seek(time);
|
|
3476
|
+
if (refresh) {
|
|
2909
3477
|
// Pass promise callbacks to `refresh`
|
|
2910
3478
|
_this.refresh().then(resolve).catch(reject);
|
|
2911
|
-
|
|
3479
|
+
}
|
|
3480
|
+
else {
|
|
2912
3481
|
resolve();
|
|
3482
|
+
}
|
|
2913
3483
|
});
|
|
2914
3484
|
};
|
|
3485
|
+
Object.defineProperty(Movie.prototype, "ready", {
|
|
3486
|
+
/**
|
|
3487
|
+
* `true` if the movie is ready for playback
|
|
3488
|
+
*/
|
|
3489
|
+
get: function () {
|
|
3490
|
+
var layersReady = this.layers.every(function (layer) { return layer.ready; });
|
|
3491
|
+
var effectsReady = this.effects.every(function (effect) { return effect.ready; });
|
|
3492
|
+
return layersReady && effectsReady;
|
|
3493
|
+
},
|
|
3494
|
+
enumerable: false,
|
|
3495
|
+
configurable: true
|
|
3496
|
+
});
|
|
2915
3497
|
Object.defineProperty(Movie.prototype, "canvas", {
|
|
2916
3498
|
/**
|
|
2917
|
-
* The
|
|
3499
|
+
* The HTML canvas element used for rendering
|
|
2918
3500
|
*/
|
|
2919
3501
|
get: function () {
|
|
2920
3502
|
return this._canvas;
|
|
@@ -2924,7 +3506,7 @@ var Movie = /** @class */ (function () {
|
|
|
2924
3506
|
});
|
|
2925
3507
|
Object.defineProperty(Movie.prototype, "cctx", {
|
|
2926
3508
|
/**
|
|
2927
|
-
* The
|
|
3509
|
+
* The canvas context used for rendering
|
|
2928
3510
|
*/
|
|
2929
3511
|
get: function () {
|
|
2930
3512
|
return this._cctx;
|
|
@@ -2934,7 +3516,7 @@ var Movie = /** @class */ (function () {
|
|
|
2934
3516
|
});
|
|
2935
3517
|
Object.defineProperty(Movie.prototype, "width", {
|
|
2936
3518
|
/**
|
|
2937
|
-
* The width of the
|
|
3519
|
+
* The width of the output canvas
|
|
2938
3520
|
*/
|
|
2939
3521
|
get: function () {
|
|
2940
3522
|
return this.canvas.width;
|
|
@@ -2947,7 +3529,7 @@ var Movie = /** @class */ (function () {
|
|
|
2947
3529
|
});
|
|
2948
3530
|
Object.defineProperty(Movie.prototype, "height", {
|
|
2949
3531
|
/**
|
|
2950
|
-
* The height of the
|
|
3532
|
+
* The height of the output canvas
|
|
2951
3533
|
*/
|
|
2952
3534
|
get: function () {
|
|
2953
3535
|
return this.canvas.height;
|
|
@@ -2959,38 +3541,46 @@ var Movie = /** @class */ (function () {
|
|
|
2959
3541
|
configurable: true
|
|
2960
3542
|
});
|
|
2961
3543
|
Object.defineProperty(Movie.prototype, "movie", {
|
|
3544
|
+
/**
|
|
3545
|
+
* @return The movie
|
|
3546
|
+
*/
|
|
2962
3547
|
get: function () {
|
|
2963
3548
|
return this;
|
|
2964
3549
|
},
|
|
2965
3550
|
enumerable: false,
|
|
2966
3551
|
configurable: true
|
|
2967
3552
|
});
|
|
3553
|
+
/**
|
|
3554
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
3555
|
+
*/
|
|
2968
3556
|
Movie.prototype.getDefaultOptions = function () {
|
|
2969
3557
|
return {
|
|
2970
3558
|
canvas: undefined,
|
|
2971
3559
|
/**
|
|
2972
3560
|
* @name module:movie#background
|
|
2973
|
-
* @desc The
|
|
3561
|
+
* @desc The color for the background, or <code>null</code> for transparency
|
|
2974
3562
|
*/
|
|
2975
|
-
background: '#000',
|
|
3563
|
+
background: parseColor('#000'),
|
|
2976
3564
|
/**
|
|
2977
3565
|
* @name module:movie#repeat
|
|
2978
3566
|
*/
|
|
2979
|
-
repeat: false
|
|
2980
|
-
/**
|
|
2981
|
-
* @name module:movie#autoRefresh
|
|
2982
|
-
* @desc Whether to refresh when changes are made that would effect the current frame
|
|
2983
|
-
*/
|
|
2984
|
-
autoRefresh: true
|
|
3567
|
+
repeat: false
|
|
2985
3568
|
};
|
|
2986
3569
|
};
|
|
2987
3570
|
return Movie;
|
|
2988
3571
|
}());
|
|
2989
|
-
//
|
|
3572
|
+
// Id for events
|
|
2990
3573
|
Movie.prototype.type = 'movie';
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3574
|
+
Movie.prototype.propertyFilters = {};
|
|
3575
|
+
deprecate('movie.audiodestinationupdate', 'audiodestinationupdate');
|
|
3576
|
+
deprecate('movie.ended', undefined);
|
|
3577
|
+
deprecate('movie.loadeddata', undefined);
|
|
3578
|
+
deprecate('movie.pause', undefined, 'Wait for `play()`, `stream()`, or `record()` to resolve instead.');
|
|
3579
|
+
deprecate('movie.play', undefined, 'Provide an `onStart` callback to `play()`, `stream()`, or `record()` instead.');
|
|
3580
|
+
deprecate('movie.record', undefined, 'Provide an `onStart` callback to `record()` instead.');
|
|
3581
|
+
deprecate('movie.recordended', undefined, 'Wait for `record()` to resolve instead.');
|
|
3582
|
+
deprecate('movie.seek', undefined, 'Override the `seek` method on layers instead.');
|
|
3583
|
+
deprecate('movie.timeupdate', undefined, 'Override the `progress` method on layers instead.');
|
|
2994
3584
|
|
|
2995
3585
|
/*
|
|
2996
3586
|
* Typedoc can't handle default exports. To let users import default export and
|
|
@@ -3000,6 +3590,7 @@ Movie.prototype.propertyFilters = {};
|
|
|
3000
3590
|
*/
|
|
3001
3591
|
|
|
3002
3592
|
var etro = /*#__PURE__*/Object.freeze({
|
|
3593
|
+
__proto__: null,
|
|
3003
3594
|
layer: index,
|
|
3004
3595
|
effect: index$1,
|
|
3005
3596
|
event: event,
|
|
@@ -3015,8 +3606,7 @@ var etro = /*#__PURE__*/Object.freeze({
|
|
|
3015
3606
|
parseColor: parseColor,
|
|
3016
3607
|
Font: Font,
|
|
3017
3608
|
parseFont: parseFont,
|
|
3018
|
-
mapPixels: mapPixels
|
|
3019
|
-
watchPublic: watchPublic
|
|
3609
|
+
mapPixels: mapPixels
|
|
3020
3610
|
});
|
|
3021
3611
|
|
|
3022
3612
|
/**
|