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.
Files changed (95) hide show
  1. package/.github/workflows/nodejs.yml +3 -1
  2. package/CHANGELOG.md +9 -1
  3. package/CODE_OF_CONDUCT.md +5 -5
  4. package/CONTRIBUTING.md +22 -72
  5. package/README.md +1 -1
  6. package/dist/effect/base.d.ts +14 -1
  7. package/dist/etro-cjs.js +156 -213
  8. package/dist/etro-iife.js +156 -213
  9. package/dist/layer/base.d.ts +13 -0
  10. package/eslint.conf.js +2 -1
  11. package/eslint.test-conf.js +1 -0
  12. package/examples/application/readme-screenshot.html +4 -8
  13. package/examples/application/video-player.html +3 -4
  14. package/examples/introduction/effects.html +23 -4
  15. package/karma.conf.js +4 -2
  16. package/package.json +4 -1
  17. package/scripts/gen-effect-samples.html +0 -3
  18. package/src/effect/base.ts +29 -10
  19. package/src/effect/gaussian-blur.ts +10 -10
  20. package/src/effect/pixelate.ts +1 -2
  21. package/src/effect/shader.ts +18 -22
  22. package/src/effect/stack.ts +5 -2
  23. package/src/effect/transform.ts +13 -14
  24. package/src/event.ts +8 -14
  25. package/src/layer/audio-source.ts +16 -14
  26. package/src/layer/audio.ts +1 -2
  27. package/src/layer/base.ts +26 -7
  28. package/src/layer/visual.ts +5 -6
  29. package/src/movie.ts +41 -49
  30. package/src/util.ts +50 -57
  31. package/docs/effect.js.html +0 -1215
  32. package/docs/event.js.html +0 -145
  33. package/docs/index.html +0 -81
  34. package/docs/index.js.html +0 -92
  35. package/docs/layer.js.html +0 -888
  36. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  37. package/docs/module-effect.Brightness.html +0 -339
  38. package/docs/module-effect.Channels.html +0 -319
  39. package/docs/module-effect.ChromaKey.html +0 -611
  40. package/docs/module-effect.Contrast.html +0 -339
  41. package/docs/module-effect.EllipticalMask.html +0 -200
  42. package/docs/module-effect.GaussianBlur.html +0 -202
  43. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  44. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  45. package/docs/module-effect.Pixelate.html +0 -330
  46. package/docs/module-effect.Shader.html +0 -1227
  47. package/docs/module-effect.Stack.html +0 -406
  48. package/docs/module-effect.Transform.Matrix.html +0 -193
  49. package/docs/module-effect.Transform.html +0 -1174
  50. package/docs/module-effect.html +0 -148
  51. package/docs/module-event.html +0 -473
  52. package/docs/module-index.html +0 -186
  53. package/docs/module-layer-Media.html +0 -1116
  54. package/docs/module-layer-MediaMixin.html +0 -164
  55. package/docs/module-layer.Audio.html +0 -1188
  56. package/docs/module-layer.Base.html +0 -629
  57. package/docs/module-layer.Image.html +0 -1421
  58. package/docs/module-layer.Text.html +0 -1731
  59. package/docs/module-layer.Video.html +0 -1938
  60. package/docs/module-layer.Visual.html +0 -1698
  61. package/docs/module-layer.html +0 -137
  62. package/docs/module-movie.html +0 -3118
  63. package/docs/module-util.Color.html +0 -702
  64. package/docs/module-util.Font.html +0 -395
  65. package/docs/module-util.html +0 -845
  66. package/docs/movie.js.html +0 -689
  67. package/docs/scripts/collapse.js +0 -20
  68. package/docs/scripts/linenumber.js +0 -25
  69. package/docs/scripts/nav.js +0 -12
  70. package/docs/scripts/polyfill.js +0 -4
  71. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  72. package/docs/scripts/prettify/lang-css.js +0 -2
  73. package/docs/scripts/prettify/prettify.js +0 -28
  74. package/docs/scripts/search.js +0 -83
  75. package/docs/styles/jsdoc.css +0 -671
  76. package/docs/styles/prettify.css +0 -79
  77. package/docs/util.js.html +0 -503
  78. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  79. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  80. package/spec/assets/effect/grayscale.png +0 -0
  81. package/spec/assets/effect/original.png +0 -0
  82. package/spec/assets/effect/pixelate.png +0 -0
  83. package/spec/assets/effect/transform/multiply.png +0 -0
  84. package/spec/assets/effect/transform/rotate.png +0 -0
  85. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  86. package/spec/assets/effect/transform/scale.png +0 -0
  87. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  88. package/spec/assets/effect/transform/translate.png +0 -0
  89. package/spec/assets/layer/audio.wav +0 -0
  90. package/spec/assets/layer/image.jpg +0 -0
  91. package/spec/effect.spec.js +0 -421
  92. package/spec/event.spec.js +0 -39
  93. package/spec/layer.spec.js +0 -307
  94. package/spec/movie.spec.js +0 -346
  95. package/spec/util.spec.js +0 -294
@@ -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
- })
@@ -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
- })