etro 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/nodejs.yml +3 -1
- package/CHANGELOG.md +9 -1
- package/CODE_OF_CONDUCT.md +5 -5
- package/CONTRIBUTING.md +22 -72
- package/README.md +1 -1
- package/dist/effect/base.d.ts +14 -1
- package/dist/etro-cjs.js +156 -213
- package/dist/etro-iife.js +156 -213
- package/dist/layer/base.d.ts +13 -0
- package/eslint.conf.js +2 -1
- package/eslint.test-conf.js +1 -0
- package/examples/application/readme-screenshot.html +4 -8
- package/examples/application/video-player.html +3 -4
- package/examples/introduction/effects.html +23 -4
- package/karma.conf.js +4 -2
- package/package.json +4 -1
- package/scripts/gen-effect-samples.html +0 -3
- package/src/effect/base.ts +29 -10
- package/src/effect/gaussian-blur.ts +10 -10
- package/src/effect/pixelate.ts +1 -2
- package/src/effect/shader.ts +18 -22
- package/src/effect/stack.ts +5 -2
- package/src/effect/transform.ts +13 -14
- package/src/event.ts +8 -14
- package/src/layer/audio-source.ts +16 -14
- package/src/layer/audio.ts +1 -2
- package/src/layer/base.ts +26 -7
- package/src/layer/visual.ts +5 -6
- package/src/movie.ts +41 -49
- package/src/util.ts +50 -57
- package/docs/effect.js.html +0 -1215
- package/docs/event.js.html +0 -145
- package/docs/index.html +0 -81
- package/docs/index.js.html +0 -92
- package/docs/layer.js.html +0 -888
- package/docs/module-effect-GaussianBlurComponent.html +0 -345
- package/docs/module-effect.Brightness.html +0 -339
- package/docs/module-effect.Channels.html +0 -319
- package/docs/module-effect.ChromaKey.html +0 -611
- package/docs/module-effect.Contrast.html +0 -339
- package/docs/module-effect.EllipticalMask.html +0 -200
- package/docs/module-effect.GaussianBlur.html +0 -202
- package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
- package/docs/module-effect.GaussianBlurVertical.html +0 -242
- package/docs/module-effect.Pixelate.html +0 -330
- package/docs/module-effect.Shader.html +0 -1227
- package/docs/module-effect.Stack.html +0 -406
- package/docs/module-effect.Transform.Matrix.html +0 -193
- package/docs/module-effect.Transform.html +0 -1174
- package/docs/module-effect.html +0 -148
- package/docs/module-event.html +0 -473
- package/docs/module-index.html +0 -186
- package/docs/module-layer-Media.html +0 -1116
- package/docs/module-layer-MediaMixin.html +0 -164
- package/docs/module-layer.Audio.html +0 -1188
- package/docs/module-layer.Base.html +0 -629
- package/docs/module-layer.Image.html +0 -1421
- package/docs/module-layer.Text.html +0 -1731
- package/docs/module-layer.Video.html +0 -1938
- package/docs/module-layer.Visual.html +0 -1698
- package/docs/module-layer.html +0 -137
- package/docs/module-movie.html +0 -3118
- package/docs/module-util.Color.html +0 -702
- package/docs/module-util.Font.html +0 -395
- package/docs/module-util.html +0 -845
- package/docs/movie.js.html +0 -689
- package/docs/scripts/collapse.js +0 -20
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/nav.js +0 -12
- package/docs/scripts/polyfill.js +0 -4
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/scripts/search.js +0 -83
- package/docs/styles/jsdoc.css +0 -671
- package/docs/styles/prettify.css +0 -79
- package/docs/util.js.html +0 -503
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
- package/spec/assets/effect/grayscale.png +0 -0
- package/spec/assets/effect/original.png +0 -0
- package/spec/assets/effect/pixelate.png +0 -0
- package/spec/assets/effect/transform/multiply.png +0 -0
- package/spec/assets/effect/transform/rotate.png +0 -0
- package/spec/assets/effect/transform/scale-fraction.png +0 -0
- package/spec/assets/effect/transform/scale.png +0 -0
- package/spec/assets/effect/transform/translate-fraction.png +0 -0
- package/spec/assets/effect/transform/translate.png +0 -0
- package/spec/assets/layer/audio.wav +0 -0
- package/spec/assets/layer/image.jpg +0 -0
- package/spec/effect.spec.js +0 -421
- package/spec/event.spec.js +0 -39
- package/spec/layer.spec.js +0 -307
- package/spec/movie.spec.js +0 -346
- package/spec/util.spec.js +0 -294
package/spec/layer.spec.js
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
describe('Layers', function () {
|
|
2
|
-
describe('Base', function () {
|
|
3
|
-
let layer
|
|
4
|
-
|
|
5
|
-
beforeEach(function () {
|
|
6
|
-
layer = new etro.layer.Base({ startTime: 0, duration: 4 })
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it("should be of type 'layer'", function () {
|
|
10
|
-
expect(layer.type).toBe('layer')
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('should attach to movie', function () {
|
|
14
|
-
const movie = {}
|
|
15
|
-
// Simulate attach to movie
|
|
16
|
-
layer.attach(movie)
|
|
17
|
-
expect(layer._movie).toEqual(movie)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should propogate changes up', function () {
|
|
21
|
-
// Connect to movie to publish event to
|
|
22
|
-
const movie = {}
|
|
23
|
-
layer.attach(movie)
|
|
24
|
-
|
|
25
|
-
// Listen for event called on moive
|
|
26
|
-
let timesFired = 0
|
|
27
|
-
etro.event.subscribe(movie, 'movie.change.layer', () => {
|
|
28
|
-
timesFired++
|
|
29
|
-
})
|
|
30
|
-
// Modify layer
|
|
31
|
-
layer.startTime = 1
|
|
32
|
-
expect(timesFired).toBe(1)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('Visual', function () {
|
|
37
|
-
let layer
|
|
38
|
-
|
|
39
|
-
beforeEach(function () {
|
|
40
|
-
layer = new etro.layer.Visual({ startTime: 0, duration: 4, background: 'blue' })
|
|
41
|
-
const movie = { width: 400, height: 400, currentTime: 0, propertyFilters: {} }
|
|
42
|
-
movie.movie = movie
|
|
43
|
-
layer.attach(movie)
|
|
44
|
-
layer.render(0)
|
|
45
|
-
// Clear cache populated by render()
|
|
46
|
-
etro.clearCachedValues(movie)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("should use the movie's width when no layer width is given", function () {
|
|
50
|
-
const width = etro.val(layer, 'width', 0)
|
|
51
|
-
expect(width).toBe(layer.movie.width)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it("should use the movie's height when no layer height is given", function () {
|
|
55
|
-
const height = etro.val(layer, 'height', 0)
|
|
56
|
-
expect(height).toBe(layer.movie.height)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('should use the width if provided', function () {
|
|
60
|
-
layer.width = 4
|
|
61
|
-
const width = etro.val(layer, 'width', 0)
|
|
62
|
-
expect(width).toBe(4)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should use the height if provided', function () {
|
|
66
|
-
layer.height = 4
|
|
67
|
-
const height = etro.val(layer, 'height', 0)
|
|
68
|
-
expect(height).toBe(4)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should render the background', function () {
|
|
72
|
-
const imageData = layer.cctx.getImageData(0, 0, 400, 400)
|
|
73
|
-
let allBlue = true
|
|
74
|
-
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
75
|
-
allBlue = allBlue &&
|
|
76
|
-
imageData.data[i + 0] === 0 &&
|
|
77
|
-
imageData.data[i + 1] === 0 &&
|
|
78
|
-
imageData.data[i + 2] === 255 &&
|
|
79
|
-
imageData.data[i + 3] === 255
|
|
80
|
-
}
|
|
81
|
-
expect(allBlue).toBe(true)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should call `attach` when an effect is added', function () {
|
|
85
|
-
const effect = new etro.effect.Base()
|
|
86
|
-
spyOn(effect, 'attach')
|
|
87
|
-
layer.effects.push(effect)
|
|
88
|
-
expect(effect.attach).toHaveBeenCalled()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should call `detach` when an effect is removed', function () {
|
|
92
|
-
const effect = new etro.effect.Base()
|
|
93
|
-
layer.effects.push(effect)
|
|
94
|
-
spyOn(effect, 'detach')
|
|
95
|
-
layer.effects.pop()
|
|
96
|
-
expect(effect.detach).toHaveBeenCalled()
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('should call `detach` when an effect is replaced', function () {
|
|
100
|
-
const effect = new etro.effect.Base()
|
|
101
|
-
layer.effects.push(effect)
|
|
102
|
-
spyOn(effect, 'detach')
|
|
103
|
-
layer.effects[0] = new etro.effect.Base()
|
|
104
|
-
expect(effect.detach).toHaveBeenCalled()
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
describe('VisualSource', function () {
|
|
109
|
-
const CustomVisualSource = etro.layer.VisualSourceMixin(etro.layer.Visual)
|
|
110
|
-
let layer
|
|
111
|
-
|
|
112
|
-
beforeEach(function (done) {
|
|
113
|
-
const image = new Image()
|
|
114
|
-
image.src = '/base/spec/assets/layer/image.jpg'
|
|
115
|
-
image.onload = () => {
|
|
116
|
-
layer = new CustomVisualSource({ startTime: 0, duration: 4, source: image })
|
|
117
|
-
// Simulate attach to movie
|
|
118
|
-
const movie = { width: image.width, height: image.height, currentTime: 0, propertyFilters: [] }
|
|
119
|
-
movie.movie = movie
|
|
120
|
-
layer.attach(movie)
|
|
121
|
-
done()
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it("should use the image source's width when no sourceWidth is provided", function () {
|
|
126
|
-
const sourceWidth = etro.val(layer, 'sourceWidth', 0)
|
|
127
|
-
expect(sourceWidth).toBe(layer.source.width)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it("should use the image source's height when no sourceHeight is provided", function () {
|
|
131
|
-
const sourceHeight = etro.val(layer, 'sourceHeight', 0)
|
|
132
|
-
expect(sourceHeight).toBe(layer.source.height)
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('should use sourceWidth when no destWidth is provided', function () {
|
|
136
|
-
const destWidth = etro.val(layer, 'destWidth', 0)
|
|
137
|
-
const sourceWidth = etro.val(layer, 'sourceWidth', 0)
|
|
138
|
-
expect(destWidth).toBe(sourceWidth)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('should use sourceHeight when no destHeight is provided', function () {
|
|
142
|
-
const destHeight = etro.val(layer, 'destHeight', 0)
|
|
143
|
-
const sourceHeight = etro.val(layer, 'sourceHeight', 0)
|
|
144
|
-
expect(destHeight).toBe(sourceHeight)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should use destWidth when no width is provided', function () {
|
|
148
|
-
const width = etro.val(layer, 'width', 0)
|
|
149
|
-
const destWidth = etro.val(layer, 'destWidth', 0)
|
|
150
|
-
expect(width).toBe(destWidth)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should use destHeight when no height is provided', function () {
|
|
154
|
-
const height = etro.val(layer, 'height', 0)
|
|
155
|
-
const destHeight = etro.val(layer, 'destHeight', 0)
|
|
156
|
-
expect(height).toBe(destHeight)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('should render', function () {
|
|
160
|
-
// Render layer (actual outcome)
|
|
161
|
-
layer.render(0)
|
|
162
|
-
const width = etro.val(layer, 'width', 0)
|
|
163
|
-
const height = etro.val(layer, 'height', 0)
|
|
164
|
-
const imageData = layer.cctx.getImageData(0, 0, width, height)
|
|
165
|
-
|
|
166
|
-
// Draw image (expected outcome)
|
|
167
|
-
const testCanv = document.createElement('canvas')
|
|
168
|
-
testCanv.width = width
|
|
169
|
-
testCanv.height = height
|
|
170
|
-
const testCtx = testCanv.getContext('2d')
|
|
171
|
-
testCtx.drawImage(layer.source, 0, 0, width, height)
|
|
172
|
-
const testImageData = testCtx.getImageData(0, 0, width, height)
|
|
173
|
-
|
|
174
|
-
// Compare expected outcome with actual outcome
|
|
175
|
-
let equal = true
|
|
176
|
-
for (let i = 0; i < imageData.data.length; i++) {
|
|
177
|
-
equal = equal && imageData.data[i] === testImageData.data[i]
|
|
178
|
-
}
|
|
179
|
-
expect(equal).toBe(true)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('should scale with `imageWidth` and `imageHeight`', function () {
|
|
183
|
-
const resizedLayer = new etro.layer.Image({
|
|
184
|
-
startTime: 0,
|
|
185
|
-
duration: 1,
|
|
186
|
-
source: layer.source,
|
|
187
|
-
destWidth: 100,
|
|
188
|
-
destHeight: 100
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
// Render layer (actual outcome)
|
|
192
|
-
const movie = {}
|
|
193
|
-
resizedLayer.attach(movie)
|
|
194
|
-
resizedLayer.render(0)
|
|
195
|
-
const imageData = resizedLayer.cctx.getImageData(0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
196
|
-
|
|
197
|
-
// Draw image (expected outcome)
|
|
198
|
-
const testCanv = document.createElement('canvas')
|
|
199
|
-
testCanv.width = resizedLayer.destWidth
|
|
200
|
-
testCanv.height = resizedLayer.destHeight
|
|
201
|
-
const testCtx = testCanv.getContext('2d')
|
|
202
|
-
testCtx.drawImage(layer.source, 0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
203
|
-
const testImageData = testCtx.getImageData(0, 0, resizedLayer.destWidth, resizedLayer.destHeight)
|
|
204
|
-
|
|
205
|
-
// Compare expected outcome with actual outcome
|
|
206
|
-
expect(imageData.data).toEqual(testImageData.data)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('should be cropped to the clip with and height', function () {
|
|
210
|
-
const newLayer = new etro.layer.Image({
|
|
211
|
-
startTime: 0,
|
|
212
|
-
duration: 1,
|
|
213
|
-
source: layer.source,
|
|
214
|
-
sourceWidth: 2,
|
|
215
|
-
sourceHeight: 3
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
// Render layer (actual outcome)
|
|
219
|
-
const movie = {}
|
|
220
|
-
newLayer.attach(movie)
|
|
221
|
-
newLayer.render(0)
|
|
222
|
-
const imageData = newLayer.cctx.getImageData(
|
|
223
|
-
0, 0, newLayer.sourceWidth, newLayer.sourceHeight
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
// Draw image (expected outcome)
|
|
227
|
-
// testCanv will contain the part of the layer with the image.
|
|
228
|
-
const testCanv = document.createElement('canvas')
|
|
229
|
-
testCanv.width = newLayer.sourceWidth
|
|
230
|
-
testCanv.height = newLayer.sourceHeight
|
|
231
|
-
const testCtx = testCanv.getContext('2d')
|
|
232
|
-
testCtx.drawImage(
|
|
233
|
-
layer.source,
|
|
234
|
-
0, 0,
|
|
235
|
-
newLayer.sourceWidth, newLayer.sourceHeight,
|
|
236
|
-
0, 0,
|
|
237
|
-
newLayer.sourceWidth, newLayer.sourceHeight
|
|
238
|
-
)
|
|
239
|
-
const testImageData = testCtx.getImageData(0, 0, newLayer.sourceWidth, newLayer.sourceHeight)
|
|
240
|
-
|
|
241
|
-
// Compare expected image data with actual image data
|
|
242
|
-
expect(imageData.data).toEqual(testImageData.data)
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
describe('Media', function () {
|
|
247
|
-
let layer
|
|
248
|
-
// Media is an abstract mixin, so make a concrete subclass here.
|
|
249
|
-
const CustomMedia = etro.layer.AudioSourceMixin(etro.layer.Base)
|
|
250
|
-
const source = new Audio()
|
|
251
|
-
|
|
252
|
-
beforeAll(function (done) {
|
|
253
|
-
source.addEventListener('canplay', done)
|
|
254
|
-
source.src = '/base/spec/assets/layer/audio.wav'
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
beforeEach(function () {
|
|
258
|
-
layer = new CustomMedia({ startTime: 0, source })
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should update its currentTime when the movie seeks', function () {
|
|
262
|
-
const movie = {
|
|
263
|
-
actx: new AudioContext(),
|
|
264
|
-
currentTime: 2 // not 0
|
|
265
|
-
}
|
|
266
|
-
layer.attach(movie)
|
|
267
|
-
etro.event.publish(movie, 'movie.seek', {})
|
|
268
|
-
expect(layer.currentTime).toBe(2)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('should have its duration depend on its playbackRate', function () {
|
|
272
|
-
const oldDuration = layer.duration
|
|
273
|
-
layer.playbackRate = 2
|
|
274
|
-
expect(layer.duration).toBe(oldDuration / 2)
|
|
275
|
-
})
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
// I suspect this doesn't work becuase of autoplay restrictions
|
|
279
|
-
/* describe('Audio', function () {
|
|
280
|
-
let layer
|
|
281
|
-
|
|
282
|
-
beforeEach(function (done) {
|
|
283
|
-
const audio = new Audio()
|
|
284
|
-
audio.src = '/base/spec/assets/layer/audio.wav'
|
|
285
|
-
// audio.muted = true // until we figure out how to allow autoplay in headless chrome
|
|
286
|
-
audio.addEventListener('loadedmetadata', () => {
|
|
287
|
-
layer = new etro.layer.Audio(0, audio)
|
|
288
|
-
layer.attach(
|
|
289
|
-
{ actx: new AudioContext(), currentTime: 0 }
|
|
290
|
-
)
|
|
291
|
-
done()
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('should play', function () {
|
|
296
|
-
let timesPlayed = 0
|
|
297
|
-
layer.source.addEventListener('play', () => {
|
|
298
|
-
timesPlayed++
|
|
299
|
-
})
|
|
300
|
-
for (let i = 0; i < 3; i++) {
|
|
301
|
-
layer.start(0) // reltime = 0s
|
|
302
|
-
layer.stop(0) // reltime = 0s
|
|
303
|
-
}
|
|
304
|
-
expect(timesPlayed).toBe(3)
|
|
305
|
-
})
|
|
306
|
-
}) */
|
|
307
|
-
})
|
package/spec/movie.spec.js
DELETED
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
describe('Movie', function () {
|
|
2
|
-
let movie, canvas
|
|
3
|
-
|
|
4
|
-
beforeEach(function () {
|
|
5
|
-
if (canvas) {
|
|
6
|
-
document.body.removeChild(canvas)
|
|
7
|
-
}
|
|
8
|
-
canvas = document.createElement('canvas')
|
|
9
|
-
// Resolutions lower than 20x20 rreslt in empty blobs.
|
|
10
|
-
canvas.width = 20
|
|
11
|
-
canvas.height = 20
|
|
12
|
-
document.body.appendChild(canvas)
|
|
13
|
-
/*
|
|
14
|
-
* Because `autoRefresh` defaults to true, any operation that could effect
|
|
15
|
-
* the current frame causes a refresh. Thus, we have to wait to the test
|
|
16
|
-
* until the movie is done refreshing, to catch all possible errors.
|
|
17
|
-
* However, errors that take place while refreshing will only cause the test
|
|
18
|
-
* to timeout, without the actual error being shown in the terminal. The
|
|
19
|
-
* current best way to debug this situation would be to open the test in
|
|
20
|
-
* 'Chrome' instead of 'ChromeHeadless' (see karma.conf.js).
|
|
21
|
-
*/
|
|
22
|
-
movie = new etro.Movie({ canvas, background: 'blue' })
|
|
23
|
-
movie.addLayer(new etro.layer.Visual({ startTime: 0, duration: 0.8 }))
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe('identity ->', function () {
|
|
27
|
-
it("should be of type 'movie'", function () {
|
|
28
|
-
expect(movie.type).toBe('movie')
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
describe('layers ->', function () {
|
|
33
|
-
it('should call `attach` when a layer is added', function (done) {
|
|
34
|
-
const layer = new etro.layer.Base({ startTime: 0, duration: 1 })
|
|
35
|
-
spyOn(layer, 'attach')
|
|
36
|
-
// Manually attach layer to movie, because `attach` is stubbed.
|
|
37
|
-
// Otherwise, auto-refresh will cause errors.
|
|
38
|
-
layer._movie = movie
|
|
39
|
-
|
|
40
|
-
// Adding a layer will cause the movie to refresh. Wait until the movie's
|
|
41
|
-
// done refreshing to end the test (in case errors arise there!)
|
|
42
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
43
|
-
// Add layer
|
|
44
|
-
movie.layers.push(layer)
|
|
45
|
-
expect(layer.attach).toHaveBeenCalled()
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should call `detach` when a layer is removed', function (done) {
|
|
49
|
-
spyOn(movie.layers[0], 'detach')
|
|
50
|
-
// Wait to end the test until the movie's done refreshing.
|
|
51
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
52
|
-
const layer = movie.layers.shift()
|
|
53
|
-
expect(layer.detach).toHaveBeenCalled()
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('should call `detach` when a layer is replaced', function (done) {
|
|
57
|
-
const layer = movie.layers[0]
|
|
58
|
-
spyOn(layer, 'detach')
|
|
59
|
-
// Wait to end the test until the movie's done refreshing.
|
|
60
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
61
|
-
movie.layers[0] = new etro.layer.Base({ startTime: 0, duration: 1 })
|
|
62
|
-
expect(layer.detach).toHaveBeenCalled()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should implement common array methods', function (done) {
|
|
66
|
-
const dummy = () => new etro.layer.Base({ startTime: 0, duration: 1 })
|
|
67
|
-
const calls = {
|
|
68
|
-
concat: [[dummy()]],
|
|
69
|
-
every: [layer => true],
|
|
70
|
-
includes: [dummy()],
|
|
71
|
-
pop: [],
|
|
72
|
-
push: [dummy()],
|
|
73
|
-
unshift: [dummy()]
|
|
74
|
-
}
|
|
75
|
-
// Wait to end the test until the movie's done refreshing.
|
|
76
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
77
|
-
for (const method in calls) {
|
|
78
|
-
const args = calls[method]
|
|
79
|
-
const copy = [...movie.layers]
|
|
80
|
-
const expectedResult = Array.prototype[method].apply(copy, args)
|
|
81
|
-
const actualResult = movie.layers[method](...args)
|
|
82
|
-
expect(actualResult).toEqual(expectedResult)
|
|
83
|
-
expect(movie.layers).toEqual(copy)
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
describe('effects ->', function () {
|
|
89
|
-
it('should call `attach` when an effect is added', function (done) {
|
|
90
|
-
const effect = new etro.effect.Base()
|
|
91
|
-
spyOn(effect, 'attach')
|
|
92
|
-
// Wait to end the test until the movie's done refreshing.
|
|
93
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
94
|
-
|
|
95
|
-
movie.effects.push(effect)
|
|
96
|
-
expect(effect.attach).toHaveBeenCalled()
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('should call `detach` when an effect is removed', function (done) {
|
|
100
|
-
const effect = new etro.effect.Base()
|
|
101
|
-
movie.effects.push(effect)
|
|
102
|
-
spyOn(effect, 'detach')
|
|
103
|
-
// Wait to end the test until the movie's done refreshing.
|
|
104
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
105
|
-
|
|
106
|
-
movie.effects.pop()
|
|
107
|
-
expect(effect.detach).toHaveBeenCalled()
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should call `detach` when an effect is replaced', function (done) {
|
|
111
|
-
const effect = new etro.effect.Base()
|
|
112
|
-
movie.effects.push(effect)
|
|
113
|
-
spyOn(effect, 'detach')
|
|
114
|
-
// Wait to end the test until the movie's done refreshing.
|
|
115
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
116
|
-
|
|
117
|
-
movie.effects[0] = new etro.effect.Base()
|
|
118
|
-
expect(effect.detach).toHaveBeenCalled()
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('should implement common array methods', function (done) {
|
|
122
|
-
const dummy = () => new etro.effect.Base()
|
|
123
|
-
const calls = {
|
|
124
|
-
concat: [[dummy()]],
|
|
125
|
-
every: [layer => true],
|
|
126
|
-
includes: [dummy()],
|
|
127
|
-
pop: [],
|
|
128
|
-
push: [dummy()],
|
|
129
|
-
unshift: [dummy()]
|
|
130
|
-
}
|
|
131
|
-
// Wait to end the test until the movie's done refreshing.
|
|
132
|
-
etro.event.subscribe(movie, 'movie.loadeddata', done)
|
|
133
|
-
|
|
134
|
-
for (const method in calls) {
|
|
135
|
-
const args = calls[method]
|
|
136
|
-
const copy = [...movie.effects]
|
|
137
|
-
const expectedResult = Array.prototype[method].apply(copy, args)
|
|
138
|
-
const actualResult = movie.effects[method](...args)
|
|
139
|
-
expect(actualResult).toEqual(expectedResult)
|
|
140
|
-
expect(movie.effects).toEqual(copy)
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
describe('operations ->', function () {
|
|
146
|
-
it('should not be paused after playing', function () {
|
|
147
|
-
movie.play()
|
|
148
|
-
expect(movie.paused).toBe(false)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('should be paused after pausing', function () {
|
|
152
|
-
movie.play()
|
|
153
|
-
movie.pause()
|
|
154
|
-
// No promise returned by `pause`, because code is async in implementation.
|
|
155
|
-
expect(movie.paused).toBe(true)
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('should be paused after stopping', function () {
|
|
159
|
-
movie.play()
|
|
160
|
-
movie.stop()
|
|
161
|
-
expect(movie.paused).toBe(true)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('should be reset to beginning after stopping', function () {
|
|
165
|
-
movie.play()
|
|
166
|
-
movie.stop()
|
|
167
|
-
expect(movie.currentTime).toBe(0)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('should be `recording` when recording', function () {
|
|
171
|
-
movie.record({ frameRate: 10 })
|
|
172
|
-
expect(movie.recording).toBe(true)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('should not be paused when recording', function () {
|
|
176
|
-
movie.record({ frameRate: 10 })
|
|
177
|
-
expect(movie.paused).toBe(false)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('should end recording at the right time when `duration` is supplied', function (done) {
|
|
181
|
-
movie.record({ frameRate: 10, duration: 0.4 })
|
|
182
|
-
.then(_ => {
|
|
183
|
-
// Expect movie.currentTime to be a little larger than 0.4 (the last render might land after 0.4)
|
|
184
|
-
expect(movie.currentTime).toBeGreaterThanOrEqual(0.4)
|
|
185
|
-
expect(movie.currentTime).toBeLessThan(0.4 + 0.08)
|
|
186
|
-
done()
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('should reach the end when recording with no `duration`', function (done) {
|
|
191
|
-
etro.event.subscribe(movie, 'movie.ended', done)
|
|
192
|
-
movie.record({ frameRate: 10 })
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('should return blob after recording', function (done) {
|
|
196
|
-
movie.record({ frameRate: 60 })
|
|
197
|
-
.then(video => {
|
|
198
|
-
expect(video.size).toBeGreaterThan(0)
|
|
199
|
-
done()
|
|
200
|
-
})
|
|
201
|
-
.catch(e => {
|
|
202
|
-
throw e
|
|
203
|
-
})
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('can record with custom MIME type', function (done) {
|
|
207
|
-
movie.record({ frameRate: 60, type: 'video/mp4' })
|
|
208
|
-
.then(video => {
|
|
209
|
-
expect(video.type).toBe('video/mp4')
|
|
210
|
-
done()
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should produce correct image data when recording', function (done) {
|
|
215
|
-
movie.record({ frameRate: 10 })
|
|
216
|
-
.then(video => {
|
|
217
|
-
// Render the first frame of the video to a canvas and make sure the
|
|
218
|
-
// image data is correct.
|
|
219
|
-
|
|
220
|
-
// Load blob into html video element
|
|
221
|
-
const v = document.createElement('video')
|
|
222
|
-
v.src = URL.createObjectURL(video)
|
|
223
|
-
// Since it's a blob, we need to force-load all frames for it to
|
|
224
|
-
// render properly, using this hack:
|
|
225
|
-
v.currentTime = Number.MAX_SAFE_INTEGER
|
|
226
|
-
v.ontimeupdate = () => {
|
|
227
|
-
// Now the video is loaded. Create temporary canvas and render first
|
|
228
|
-
// frame onto it.
|
|
229
|
-
const ctx = document
|
|
230
|
-
.createElement('canvas')
|
|
231
|
-
.getContext('2d')
|
|
232
|
-
ctx.canvas.width = v.videoWidth
|
|
233
|
-
ctx.canvas.height = v.videoHeight
|
|
234
|
-
ctx.drawImage(v, 0, 0)
|
|
235
|
-
// Expect all opaque blue pixels
|
|
236
|
-
const expectedImageData = Array(v.videoWidth * v.videoHeight)
|
|
237
|
-
.fill([0, 0, 255, 255])
|
|
238
|
-
.flat(1)
|
|
239
|
-
const actualImageData = Array.from(
|
|
240
|
-
ctx.getImageData(0, 0, v.videoWidth, v.videoHeight).data
|
|
241
|
-
)
|
|
242
|
-
const maxDiff = actualImageData
|
|
243
|
-
// Calculate diff image data
|
|
244
|
-
.map((x, i) => x - expectedImageData[i])
|
|
245
|
-
// Find max pixel component diff
|
|
246
|
-
.reduce((x, max) => Math.max(x, max))
|
|
247
|
-
|
|
248
|
-
// Now, there is going to be variance due to encoding problems.
|
|
249
|
-
// Accept an error of 5 for each color component (5 is somewhat
|
|
250
|
-
// arbitrary but it works).
|
|
251
|
-
expect(maxDiff).toBeLessThanOrEqual(5)
|
|
252
|
-
URL.revokeObjectURL(v.src)
|
|
253
|
-
done()
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
describe('events ->', function () {
|
|
260
|
-
it("should fire 'movie.play' once", function () {
|
|
261
|
-
let timesFired = 0
|
|
262
|
-
etro.event.subscribe(movie, 'movie.play', function () {
|
|
263
|
-
timesFired++
|
|
264
|
-
})
|
|
265
|
-
movie.play().then(function () {
|
|
266
|
-
expect(timesFired).toBe(1)
|
|
267
|
-
})
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
it("should fire 'movie.pause' once", function () {
|
|
271
|
-
let timesFired = 0
|
|
272
|
-
etro.event.subscribe(movie, 'movie.pause', function () {
|
|
273
|
-
timesFired++
|
|
274
|
-
})
|
|
275
|
-
// play, pause and check if event was fired
|
|
276
|
-
movie.play().then(function () {
|
|
277
|
-
movie.pause()
|
|
278
|
-
expect(timesFired).toBe(1)
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
it("should fire 'movie.record' once", function () {
|
|
283
|
-
let timesFired = 0
|
|
284
|
-
etro.event.subscribe(movie, 'movie.record', function () {
|
|
285
|
-
timesFired++
|
|
286
|
-
})
|
|
287
|
-
movie.record({ frameRate: 1 }).then(function () {
|
|
288
|
-
expect(timesFired).toBe(1)
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
it("should fire 'movie.record' with correct options", function () {
|
|
293
|
-
const options = {
|
|
294
|
-
video: true, // even default values should be passed (exactly what user provides)
|
|
295
|
-
audio: false
|
|
296
|
-
}
|
|
297
|
-
etro.event.subscribe(movie, 'movie.record', function (event) {
|
|
298
|
-
expect(event.options).toEqual(options)
|
|
299
|
-
})
|
|
300
|
-
movie.record(options)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
it("should fire 'movie.ended'", function () {
|
|
304
|
-
let timesFired = 0
|
|
305
|
-
etro.event.subscribe(movie, 'movie.ended', function () {
|
|
306
|
-
timesFired++
|
|
307
|
-
})
|
|
308
|
-
movie.play().then(function () {
|
|
309
|
-
expect(timesFired).toBe(1)
|
|
310
|
-
})
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it("should fire 'movie.loadeddata'", function () {
|
|
314
|
-
/*
|
|
315
|
-
* 'loadeddata' gets timesFired when when the frame is fully loaded
|
|
316
|
-
*/
|
|
317
|
-
|
|
318
|
-
let firedOnce = false
|
|
319
|
-
etro.event.subscribe(movie, 'movie.loadeddata', () => {
|
|
320
|
-
firedOnce = true
|
|
321
|
-
})
|
|
322
|
-
movie.refresh().then(() => {
|
|
323
|
-
expect(firedOnce).toBe(true)
|
|
324
|
-
})
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it("should fire 'movie.seek'", function () {
|
|
328
|
-
let timesFired = 0
|
|
329
|
-
etro.event.subscribe(movie, 'movie.seek', function () {
|
|
330
|
-
timesFired++
|
|
331
|
-
})
|
|
332
|
-
movie.currentTime = movie.duration / 2
|
|
333
|
-
expect(timesFired).toBe(1)
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
it("should fire 'movie.timeupdate'", function () {
|
|
337
|
-
let firedOnce = false
|
|
338
|
-
etro.event.subscribe(movie, 'movie.timeupdate', function () {
|
|
339
|
-
firedOnce = true
|
|
340
|
-
})
|
|
341
|
-
movie.play().then(function () {
|
|
342
|
-
expect(firedOnce).toBe(true)
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
})
|
|
346
|
-
})
|