etro 0.6.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.
Files changed (103) hide show
  1. package/.env +2 -0
  2. package/.github/workflows/nodejs.yml +27 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CODE_OF_CONDUCT.md +77 -0
  5. package/CONTRIBUTING.md +155 -0
  6. package/LICENSE +674 -0
  7. package/README.md +57 -0
  8. package/dist/etro.js +3397 -0
  9. package/docs/effect.js.html +1215 -0
  10. package/docs/event.js.html +145 -0
  11. package/docs/index.html +81 -0
  12. package/docs/index.js.html +92 -0
  13. package/docs/layer.js.html +888 -0
  14. package/docs/module-effect-GaussianBlurComponent.html +345 -0
  15. package/docs/module-effect.Brightness.html +339 -0
  16. package/docs/module-effect.Channels.html +319 -0
  17. package/docs/module-effect.ChromaKey.html +611 -0
  18. package/docs/module-effect.Contrast.html +339 -0
  19. package/docs/module-effect.EllipticalMask.html +200 -0
  20. package/docs/module-effect.GaussianBlur.html +202 -0
  21. package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
  22. package/docs/module-effect.GaussianBlurVertical.html +242 -0
  23. package/docs/module-effect.Pixelate.html +330 -0
  24. package/docs/module-effect.Shader.html +1227 -0
  25. package/docs/module-effect.Stack.html +406 -0
  26. package/docs/module-effect.Transform.Matrix.html +193 -0
  27. package/docs/module-effect.Transform.html +1174 -0
  28. package/docs/module-effect.html +148 -0
  29. package/docs/module-event.html +473 -0
  30. package/docs/module-index.html +186 -0
  31. package/docs/module-layer-Media.html +1116 -0
  32. package/docs/module-layer-MediaMixin.html +164 -0
  33. package/docs/module-layer.Audio.html +1188 -0
  34. package/docs/module-layer.Base.html +629 -0
  35. package/docs/module-layer.Image.html +1421 -0
  36. package/docs/module-layer.Text.html +1731 -0
  37. package/docs/module-layer.Video.html +1938 -0
  38. package/docs/module-layer.Visual.html +1698 -0
  39. package/docs/module-layer.html +137 -0
  40. package/docs/module-movie.html +3118 -0
  41. package/docs/module-util.Color.html +702 -0
  42. package/docs/module-util.Font.html +395 -0
  43. package/docs/module-util.html +845 -0
  44. package/docs/movie.js.html +689 -0
  45. package/docs/scripts/collapse.js +20 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/nav.js +12 -0
  48. package/docs/scripts/polyfill.js +4 -0
  49. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  50. package/docs/scripts/prettify/lang-css.js +2 -0
  51. package/docs/scripts/prettify/prettify.js +28 -0
  52. package/docs/scripts/search.js +83 -0
  53. package/docs/styles/jsdoc.css +671 -0
  54. package/docs/styles/prettify.css +79 -0
  55. package/docs/util.js.html +503 -0
  56. package/eslint.conf.js +28 -0
  57. package/eslint.test-conf.js +4 -0
  58. package/examples/application/readme-screenshot.html +86 -0
  59. package/examples/application/video-player.html +131 -0
  60. package/examples/application/webcam.html +28 -0
  61. package/examples/introduction/audio.html +52 -0
  62. package/examples/introduction/effects.html +56 -0
  63. package/examples/introduction/export.html +70 -0
  64. package/examples/introduction/functions.html +35 -0
  65. package/examples/introduction/hello-world1.html +33 -0
  66. package/examples/introduction/hello-world2.html +32 -0
  67. package/examples/introduction/keyframes.html +67 -0
  68. package/examples/introduction/media.html +55 -0
  69. package/examples/introduction/text.html +27 -0
  70. package/jsdoc.conf.json +3 -0
  71. package/karma.conf.js +60 -0
  72. package/package.json +63 -0
  73. package/private-todo.txt +70 -0
  74. package/rename-file.sh +18 -0
  75. package/rename-versions.sh +14 -0
  76. package/rename.sh +22 -0
  77. package/rollup.config.js +31 -0
  78. package/screenshots/2019-08-17_0.png +0 -0
  79. package/scripts/gen-effect-samples.html +99 -0
  80. package/scripts/save-effect-samples.js +43 -0
  81. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  82. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  83. package/spec/assets/effect/original.png +0 -0
  84. package/spec/assets/effect/pixelate.png +0 -0
  85. package/spec/assets/effect/transform/multiply.png +0 -0
  86. package/spec/assets/effect/transform/rotate.png +0 -0
  87. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  88. package/spec/assets/effect/transform/scale.png +0 -0
  89. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  90. package/spec/assets/effect/transform/translate.png +0 -0
  91. package/spec/assets/layer/audio.wav +0 -0
  92. package/spec/assets/layer/image.jpg +0 -0
  93. package/spec/effect.spec.js +352 -0
  94. package/spec/event.spec.js +25 -0
  95. package/spec/layer.spec.js +128 -0
  96. package/spec/movie.spec.js +154 -0
  97. package/spec/util.spec.js +285 -0
  98. package/src/effect.js +1265 -0
  99. package/src/event.js +78 -0
  100. package/src/index.js +23 -0
  101. package/src/layer.js +875 -0
  102. package/src/movie.js +636 -0
  103. package/src/util.js +487 -0
@@ -0,0 +1,888 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>layer.js - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
18
+ </head>
19
+ <body>
20
+
21
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
22
+ <label for="nav-trigger" class="navicon-button x">
23
+ <div class="navicon"></div>
24
+ </label>
25
+
26
+ <label for="nav-trigger" class="overlay"></label>
27
+
28
+ <nav >
29
+
30
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="module-effect.Brightness.html">Brightness</a></li><li><a href="module-effect.Channels.html">Channels</a></li><li><a href="module-effect.ChromaKey.html">ChromaKey</a></li><li><a href="module-effect.Contrast.html">Contrast</a></li><li><a href="module-effect.EllipticalMask.html">EllipticalMask</a></li><li><a href="module-effect.GaussianBlur.html">GaussianBlur</a></li><li><a href="module-effect.GaussianBlurHorizontal.html">GaussianBlurHorizontal</a></li><li><a href="module-effect.GaussianBlurVertical.html">GaussianBlurVertical</a></li><li><a href="module-effect.Pixelate.html">Pixelate</a></li><li><a href="module-effect.Shader.html">Shader</a><ul class='methods'><li data-type='method'><a href="module-effect.Shader.html#._initBuffer">_initBuffer</a></li><li data-type='method'><a href="module-effect.Shader.html#._loadTexture">_loadTexture</a></li><li data-type='method'><a href="module-effect.Shader.html#_prepareValue">_prepareValue</a></li></ul></li><li><a href="module-effect.Stack.html">Stack</a><ul class='methods'><li data-type='method'><a href="module-effect.Stack.html#addEffect">addEffect</a></li></ul></li><li><a href="module-effect.Transform.html">Transform</a><ul class='methods'><li data-type='method'><a href="module-effect.Transform.html#.Matrix#cell">Matrix#cell</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#multiply">Matrix#multiply</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#rotate">Matrix#rotate</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#scale">Matrix#scale</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#translate">Matrix#translate</a></li></ul></li><li><a href="module-effect.Transform.Matrix.html">Matrix</a></li><li><a href="module-effect-GaussianBlurComponent.html">GaussianBlurComponent</a></li><li><a href="module-layer.Audio.html">Audio</a></li><li><a href="module-layer.Base.html">Base</a><ul class='methods'><li data-type='method'><a href="module-layer.Base.html#_render">_render</a></li></ul></li><li><a href="module-layer.Image.html">Image</a></li><li><a href="module-layer.Text.html">Text</a></li><li><a href="module-layer.Video.html">Video</a></li><li><a href="module-layer.Visual.html">Visual</a><ul class='methods'><li data-type='method'><a href="module-layer.Visual.html#_render">_render</a></li><li data-type='method'><a href="module-layer.Visual.html#addEffect">addEffect</a></li></ul></li><li><a href="module-layer-Media.html">Media</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.Color.html">Color</a><ul class='methods'><li data-type='method'><a href="module-util.Color.html#toString">toString</a></li></ul></li><li><a href="module-util.Font.html">Font</a><ul class='methods'><li data-type='method'><a href="module-util.Font.html#toString">toString</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-effect.html">effect</a></li><li><a href="module-event.html">event</a><ul class='methods'><li data-type='method'><a href="module-event.html#.publish">publish</a></li><li data-type='method'><a href="module-event.html#.subscribe">subscribe</a></li></ul></li><li><a href="module-index.html">index</a></li><li><a href="module-layer.html">layer</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.html">util</a><ul class='methods'><li data-type='method'><a href="module-util.html#.parseColor">parseColor</a></li><li data-type='method'><a href="module-util.html#.parseFont">parseFont</a></li><li data-type='method'><a href="module-util.html#.watchPublic">watchPublic</a></li><li data-type='method'><a href="module-util.html#~isKeyFrames">isKeyFrames</a></li></ul></li></ul><h3>Mixins</h3><ul><li><a href="module-layer-MediaMixin.html">MediaMixin</a></li></ul>
31
+ </nav>
32
+
33
+ <div id="main">
34
+
35
+ <h1 class="page-title">layer.js</h1>
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+ <section>
44
+ <article>
45
+ <pre class="prettyprint source linenums"><code>/**
46
+ * @module layer
47
+ * @todo Add aligning options, like horizontal and vertical align modes
48
+ */
49
+
50
+ import { publish, subscribe } from './event.js'
51
+ import { watchPublic, val, applyOptions } from './util.js'
52
+
53
+ /**
54
+ * A layer is a piece of content for the movie
55
+ */
56
+ export class Base {
57
+ /**
58
+ * Creates a new empty layer
59
+ *
60
+ * @param {number} startTime - when to start the layer on the movie's timeline
61
+ * @param {number} duration - how long the layer should last on the movie's timeline
62
+ * @param {object} [options] - no options, here for consistency
63
+ */
64
+ constructor (startTime, duration, options = {}) { // rn, options isn't used but I'm keeping it here
65
+ const newThis = watchPublic(this) // proxy that will be returned by constructor
66
+ // Don't send updates when initializing, so use this instead of newThis:
67
+ applyOptions(options, this) // no options rn, but just to stick to protocol
68
+
69
+ this._startTime = startTime
70
+ this._duration = duration
71
+
72
+ this._active = false // whether newThis layer is currently being rendered
73
+
74
+ // on attach to movie
75
+ subscribe(newThis, 'layer.attach', event => {
76
+ newThis._movie = event.movie
77
+ })
78
+
79
+ // Propogate up to target
80
+ subscribe(newThis, 'layer.change', event => {
81
+ const typeOfChange = event.type.substring(event.type.lastIndexOf('.') + 1)
82
+ const type = `movie.change.layer.${typeOfChange}`
83
+ publish(newThis._movie, type, { ...event, target: newThis._movie, source: event.source || newThis, type })
84
+ })
85
+
86
+ return newThis
87
+ }
88
+
89
+ /**
90
+ * Generic step function
91
+ * @todo rename to &lt;code>render&lt;/code>
92
+ */
93
+ _render () {}
94
+
95
+ get _parent () {
96
+ return this._movie
97
+ }
98
+
99
+ /**
100
+ * If the attached movie's playback position is in this layer
101
+ * @type boolean
102
+ */
103
+ get active () {
104
+ return this._active
105
+ }
106
+
107
+ /**
108
+ * @type number
109
+ */
110
+ get startTime () {
111
+ return this._startTime
112
+ }
113
+
114
+ set startTime (val) {
115
+ this._startTime = val
116
+ }
117
+
118
+ /**
119
+ * @type number
120
+ */
121
+ get duration () {
122
+ return this._duration
123
+ }
124
+
125
+ set duration (val) {
126
+ this._duration = val
127
+ }
128
+ }
129
+ // id for events (independent of instance, but easy to access when on prototype chain)
130
+ Base.prototype._type = 'layer'
131
+
132
+ Base.prototype.getDefaultOptions = function () {
133
+ return {}
134
+ }
135
+ Base.prototype._publicExcludes = []
136
+
137
+ /** Any layer that renders to a canvas */
138
+ export class Visual extends Base {
139
+ /**
140
+ * Creates a visual layer
141
+ *
142
+ * @param {number} startTime - when to start the layer on the movie's timeline
143
+ * @param {number} duration - how long the layer should last on the movie's timeline
144
+ * @param {object} [options] - various optional arguments
145
+ * @param {number} [options.width=null] - the width of the entire layer
146
+ * @param {number} [options.height=null] - the height of the entire layer
147
+ * @param {number} [options.x=0] - the offset of the layer relative to the movie
148
+ * @param {number} [options.y=0] - the offset of the layer relative to the movie
149
+ * @param {string} [options.background=null] - the background color of the layer, or &lt;code>null&lt;/code>
150
+ * for a transparent background
151
+ * @param {object} [options.border=null] - the layer's outline, or &lt;code>null&lt;/code> for no outline
152
+ * @param {string} [options.border.color] - the outline's color; required for a border
153
+ * @param {string} [options.border.thickness=1] - the outline's weight
154
+ * @param {number} [options.opacity=1] - the layer's opacity; &lt;code>1&lt;/cod> for full opacity
155
+ * and &lt;code>0&lt;/code> for full transparency
156
+ */
157
+ constructor (startTime, duration, options = {}) {
158
+ super(startTime, duration, options)
159
+ // only validate extra if not subclassed, because if subclcass, there will be extraneous options
160
+ applyOptions(options, this)
161
+
162
+ this._canvas = document.createElement('canvas')
163
+ this._cctx = this.canvas.getContext('2d')
164
+
165
+ this._effectsBack = []
166
+ const that = this
167
+ this._effects = new Proxy(this._effectsBack, {
168
+ apply: function (target, thisArg, argumentsList) {
169
+ return thisArg[target].apply(this, argumentsList)
170
+ },
171
+ deleteProperty: function (target, property) {
172
+ return true
173
+ },
174
+ set: function (target, property, value, receiver) {
175
+ target[property] = value
176
+ if (!isNaN(property)) { // if property is an number (index)
177
+ publish(value, 'effect.attach', { source: that })
178
+ }
179
+ return true
180
+ }
181
+ })
182
+ }
183
+
184
+ /**
185
+ * Render visual output
186
+ */
187
+ _render (reltime) {
188
+ this._beginRender(reltime)
189
+ this._doRender(reltime)
190
+ this._endRender(reltime)
191
+ }
192
+
193
+ _beginRender (reltime) {
194
+ // if this.width or this.height is null, that means "take all available screen space", so set it to
195
+ // this._move.width or this._movie.height, respectively
196
+ const w = val(this.width || this._movie.width, this, reltime)
197
+ const h = val(this.height || this._movie.height, this, reltime)
198
+ this.canvas.width = w
199
+ this.canvas.height = h
200
+ this.cctx.globalAlpha = val(this.opacity, this, reltime)
201
+ }
202
+
203
+ _doRender (reltime) {
204
+ // if this.width or this.height is null, that means "take all available screen space", so set it to
205
+ // this._move.width or this._movie.height, respectively
206
+ // canvas.width &amp; canvas.height are already interpolated
207
+ if (this.background) {
208
+ this.cctx.fillStyle = val(this.background, this, reltime)
209
+ this.cctx.fillRect(0, 0, this.canvas.width, this.canvas.height) // (0, 0) relative to layer
210
+ }
211
+ if (this.border &amp;&amp; this.border.color) {
212
+ this.cctx.strokeStyle = val(this.border.color, this, reltime)
213
+ this.cctx.lineWidth = val(this.border.thickness, this, reltime) || 1 // this is optional.. TODO: integrate this with defaultOptions
214
+ }
215
+ }
216
+
217
+ _endRender (reltime) {
218
+ const w = val(this.width || this._movie.width, this, reltime)
219
+ const h = val(this.height || this._movie.height, this, reltime)
220
+ if (w * h > 0) {
221
+ this._applyEffects()
222
+ }
223
+ // else InvalidStateError for drawing zero-area image in some effects, right?
224
+ }
225
+
226
+ _applyEffects () {
227
+ for (let i = 0; i &lt; this.effects.length; i++) {
228
+ const effect = this.effects[i]
229
+ effect.apply(this, this._movie.currentTime - this.startTime) // pass relative time
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Convienence method for &lt;code>effects.push()&lt;/code>
235
+ * @param {BaseEffect} effect
236
+ * @return {module:layer.Visual} the layer (for chaining)
237
+ */
238
+ addEffect (effect) {
239
+ this.effects.push(effect); return this
240
+ }
241
+
242
+ /**
243
+ * The intermediate rendering canvas
244
+ * @type HTMLCanvasElement
245
+ */
246
+ get canvas () {
247
+ return this._canvas
248
+ }
249
+
250
+ /**
251
+ * The context of {@link module:layer.Visual#canvas}
252
+ * @type CanvasRenderingContext2D
253
+ */
254
+ get cctx () {
255
+ return this._cctx
256
+ }
257
+
258
+ /**
259
+ * @type effect.Base[]
260
+ */
261
+ get effects () {
262
+ return this._effects // priavte (because it's a proxy)
263
+ }
264
+ }
265
+ // TODO: move these inside class declaration?
266
+ Visual.prototype.getDefaultOptions = function () {
267
+ return {
268
+ ...Base.prototype.getDefaultOptions(),
269
+ /**
270
+ * @name module:layer.Visual#x
271
+ * @type number
272
+ * @desc The offset of the layer relative to the movie
273
+ */
274
+ x: 0,
275
+ /**
276
+ * @name module:layer.Visual#y
277
+ * @type number
278
+ * @desc The offset of the layer relative to the movie
279
+ */
280
+ y: 0,
281
+ /**
282
+ * @name module:layer.Visual#width
283
+ * @type number
284
+ */
285
+ width: null,
286
+ /**
287
+ * @name module:layer.Visual#height
288
+ * @type number
289
+ */
290
+ height: null,
291
+ /**
292
+ * @name module:layer.Visual#background
293
+ * @type string
294
+ * @desc The css color code for the background, or &lt;code>null&lt;/code> for transparency
295
+ */
296
+ background: null,
297
+ /**
298
+ * @name module:layer.Visual#border
299
+ * @type string
300
+ * @desc The css border style, or &lt;code>null&lt;/code> for no border
301
+ */
302
+ border: null,
303
+ /**
304
+ * @name module:layer.Visual#opacity
305
+ * @type number
306
+ */
307
+ opacity: 1
308
+ }
309
+ }
310
+ Visual.prototype._publicExcludes = Base.prototype._publicExcludes.concat(['canvas', 'cctx', 'effects'])
311
+
312
+ export class Text extends Visual {
313
+ // TODO: is textX necessary? it seems inconsistent, because you can't define width/height directly for a text layer
314
+ /**
315
+ * Creates a new text layer
316
+ *
317
+ * @param {number} startTime
318
+ * @param {number} duration
319
+ * @param {string} text - the text to display
320
+ * @param {number} width - the width of the entire layer
321
+ * @param {number} height - the height of the entire layer
322
+ * @param {object} [options] - various optional arguments
323
+ * @param {number} [options.x=0] - the horizontal position of the layer (relative to the movie)
324
+ * @param {number} [options.y=0] - the vertical position of the layer (relative to the movie)
325
+ * @param {string} [options.background=null] - the background color of the layer, or &lt;code>null&lt;/code>
326
+ * for a transparent background
327
+ * @param {object} [options.border=null] - the layer's outline, or &lt;code>null&lt;/code> for no outline
328
+ * @param {string} [options.border.color] - the outline"s color; required for a border
329
+ * @param {string} [options.border.thickness=1] - the outline"s weight
330
+ * @param {number} [options.opacity=1] - the layer"s opacity; &lt;code>1&lt;/cod> for full opacity
331
+ * and &lt;code>0&lt;/code> for full transparency
332
+ * @param {string} [options.font="10px sans-serif"]
333
+ * @param {string} [options.color="#fff"]
334
+ * @param {number} [options.textX=0] - the text's horizontal offset relative to the layer
335
+ * @param {number} [options.textY=0] - the text's vertical offset relative to the layer
336
+ * @param {number} [options.maxWidth=null] - the maximum width of a line of text
337
+ * @param {string} [options.textAlign="start"] - horizontal align
338
+ * @param {string} [options.textBaseline="top"] - vertical align
339
+ * @param {string} [options.textDirection="ltr"] - the text direction
340
+ *
341
+ * @todo add padding options
342
+ */
343
+ constructor (startTime, duration, text, options = {}) {
344
+ // default to no (transparent) background
345
+ super(startTime, duration, { background: null, ...options }) // fill in zeros in |_doRender|
346
+ applyOptions(options, this)
347
+
348
+ /**
349
+ * @type string
350
+ */
351
+ this.text = text
352
+
353
+ // this._prevText = undefined;
354
+ // // because the canvas context rounds font size, but we need to be more accurate
355
+ // // rn, this doesn't make a difference, because we can only measure metrics by integer font sizes
356
+ // this._lastFont = undefined;
357
+ // this._prevMaxWidth = undefined;
358
+ }
359
+
360
+ _doRender (reltime) {
361
+ super._doRender(reltime)
362
+ const text = val(this.text, this, reltime); const font = val(this.font, this, reltime)
363
+ const maxWidth = this.maxWidth ? val(this.maxWidth, this, reltime) : undefined
364
+ // // properties that affect metrics
365
+ // if (this._prevText !== text || this._prevFont !== font || this._prevMaxWidth !== maxWidth)
366
+ // this._updateMetrics(text, font, maxWidth);
367
+
368
+ this.cctx.font = font
369
+ this.cctx.fillStyle = val(this.color, this, reltime)
370
+ this.cctx.textAlign = val(this.textAlign, this, reltime)
371
+ this.cctx.textBaseline = val(this.textBaseline, this, reltime)
372
+ this.cctx.textDirection = val(this.textDirection, this, reltime)
373
+ this.cctx.fillText(
374
+ text, val(this.textX, this, reltime), val(this.textY, this, reltime),
375
+ maxWidth
376
+ )
377
+
378
+ this._prevText = text
379
+ this._prevFont = font
380
+ this._prevMaxWidth = maxWidth
381
+ }
382
+
383
+ // _updateMetrics(text, font, maxWidth) {
384
+ // // TODO calculate / measure for non-integer font.size values
385
+ // let metrics = Text._measureText(text, font, maxWidth);
386
+ // // TODO: allow user-specified/overwritten width/height
387
+ // this.width = /*this.width || */metrics.width;
388
+ // this.height = /*this.height || */metrics.height;
389
+ // }
390
+
391
+ // TODO: implement setters and getters that update dimensions!
392
+
393
+ /* static _measureText(text, font, maxWidth) {
394
+ // TODO: fix too much bottom padding
395
+ const s = document.createElement("span");
396
+ s.textContent = text;
397
+ s.style.font = font;
398
+ s.style.padding = "0";
399
+ if (maxWidth) s.style.maxWidth = maxWidth;
400
+ document.body.appendChild(s);
401
+ const metrics = {width: s.offsetWidth, height: s.offsetHeight};
402
+ document.body.removeChild(s);
403
+ return metrics;
404
+ } */
405
+ }
406
+ Text.prototype.getDefaultOptions = function () {
407
+ return {
408
+ ...Visual.prototype.getDefaultOptions(),
409
+ background: null,
410
+ /**
411
+ * @name module:layer.Text#font
412
+ * @type string
413
+ * @desc The css font to render with
414
+ */
415
+ font: '10px sans-serif',
416
+ /**
417
+ * @name module:layer.Text#font
418
+ * @type string
419
+ * @desc The css color to render with
420
+ */
421
+ color: '#fff',
422
+ /**
423
+ * @name module:layer.Text#textX
424
+ * @type number
425
+ * @desc Offset of the text relative to the layer
426
+ */
427
+ textX: 0,
428
+ /**
429
+ * @name module:layer.Text#textY
430
+ * @type number
431
+ * @desc Offset of the text relative to the layer
432
+ */
433
+ textY: 0,
434
+ /**
435
+ * @name module:layer.Text#maxWidth
436
+ * @type number
437
+ */
438
+ maxWidth: null,
439
+ /**
440
+ * @name module:layer.Text#textAlign
441
+ * @type string
442
+ * @desc The horizontal alignment
443
+ * @see [&lt;code>CanvasRenderingContext2D#textAlign&lt;/code>]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign}
444
+ */
445
+ textAlign: 'start',
446
+ /**
447
+ * @name module:layer.Text#textAlign
448
+ * @type string
449
+ * @desc the vertical alignment
450
+ * @see [&lt;code>CanvasRenderingContext2D#textBaseline&lt;/code>]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline}
451
+ */
452
+ textBaseline: 'top',
453
+ /**
454
+ * @name module:layer.Text#textDirection
455
+ * @type string
456
+ * @see [&lt;code>CanvasRenderingContext2D#direction&lt;/code>]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline}
457
+ */
458
+ textDirection: 'ltr'
459
+ }
460
+ }
461
+
462
+ export class Image extends Visual {
463
+ /**
464
+ * Creates a new image layer
465
+ *
466
+ * @param {number} startTime
467
+ * @param {number} duration
468
+ * @param {HTMLImageElement} image
469
+ * @param {object} [options]
470
+ * @param {number} [options.x=0] - the offset of the layer relative to the movie
471
+ * @param {number} [options.y=0] - the offset of the layer relative to the movie
472
+ * @param {string} [options.background=null] - the background color of the layer, or &lt;code>null&lt;/code>
473
+ * for transparency
474
+ * @param {object} [options.border=null] - the layer"s outline, or &lt;code>null&lt;/code> for no outline
475
+ * @param {string} [options.border.color] - the outline"s color; required for a border
476
+ * @param {string} [options.border.thickness=1] - the outline"s weight
477
+ * @param {number} [options.opacity=1] - the layer"s opacity; &lt;code>1&lt;/cod> for full opacity
478
+ * and &lt;code>0&lt;/code> for full transparency
479
+ * @param {number} [options.clipX=0] - image source x
480
+ * @param {number} [options.clipY=0] - image source y
481
+ * @param {number} [options.clipWidth=undefined] - image source width, or &lt;code>undefined&lt;/code> to fill the entire layer
482
+ * @param {number} [options.clipHeight=undefined] - image source height, or &lt;code>undefined&lt;/code> to fill the entire layer
483
+ * @param {number} [options.imageX=0] - offset of the image relative to the layer
484
+ * @param {number} [options.imageY=0] - offset of the image relative to the layer
485
+ */
486
+ constructor (startTime, duration, image, options = {}) {
487
+ super(startTime, duration, options) // wait to set width &amp; height
488
+ applyOptions(options, this)
489
+ // clipX... => how much to show of this.image
490
+ // imageX... => how to project this.image onto the canvas
491
+ this._image = image
492
+
493
+ const load = () => {
494
+ this.width = this.imageWidth = this.width || this.image.width
495
+ this.height = this.imageHeight = this.height || this.image.height
496
+ this.clipWidth = this.clipWidth || image.width
497
+ this.clipHeight = this.clipHeight || image.height
498
+ }
499
+ if (image.complete) {
500
+ load()
501
+ } else {
502
+ image.addEventListener('load', load)
503
+ }
504
+ }
505
+
506
+ _doRender (reltime) {
507
+ super._doRender(reltime) // clear/fill background
508
+ this.cctx.drawImage(
509
+ this.image,
510
+ val(this.clipX, this, reltime), val(this.clipY, this, reltime),
511
+ val(this.clipWidth, this, reltime), val(this.clipHeight, this, reltime),
512
+ // this.imageX and this.imageY are relative to layer
513
+ val(this.imageX, this, reltime), val(this.imageY, this, reltime),
514
+ val(this.imageWidth, this, reltime), val(this.imageHeight, this, reltime)
515
+ )
516
+ }
517
+
518
+ /**
519
+ * @type HTMLImageElement
520
+ */
521
+ get image () {
522
+ return this._image
523
+ }
524
+ }
525
+ Image.prototype.getDefaultOptions = function () {
526
+ return {
527
+ ...Visual.prototype.getDefaultOptions(),
528
+ /**
529
+ * @name module:layer.Image#clipX
530
+ * @type number
531
+ * @desc Image source x
532
+ */
533
+ clipX: 0,
534
+ /**
535
+ * @name module:layer.Image#clipY
536
+ * @type number
537
+ * @desc Image source y
538
+ */
539
+ clipY: 0,
540
+ /**
541
+ * @name module:layer.Image#clipWidth
542
+ * @type number
543
+ * @desc Image source width, or &lt;code>undefined&lt;/code> to fill the entire layer
544
+ */
545
+ clipWidth: undefined,
546
+ /**
547
+ * @name module:layer.Image#clipHeight
548
+ * @type number
549
+ * @desc Image source height, or &lt;code>undefined&lt;/code> to fill the entire layer
550
+ */
551
+ clipHeight: undefined,
552
+ /**
553
+ * @name module:layer.Image#imageX
554
+ * @type number
555
+ * @desc Offset of the image relative to the layer
556
+ */
557
+ imageX: 0,
558
+ /**
559
+ * @name module:layer.Image#imageX
560
+ * @type number
561
+ * @desc Offset of the image relative to the layer
562
+ */
563
+ imageY: 0
564
+ }
565
+ }
566
+
567
+ // https://web.archive.org/web/20190111044453/http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
568
+ /**
569
+ * Video or audio
570
+ * @mixin MediaMixin
571
+ * @todo implement playback rate
572
+ */
573
+ export const MediaMixin = superclass => {
574
+ if (superclass !== Base &amp;&amp; superclass !== Visual) {
575
+ throw new Error('Media can only extend Base and Visual')
576
+ }
577
+
578
+ class Media extends superclass {
579
+ /**
580
+ * @param {number} startTime
581
+ * @param {HTMLVideoElement} media
582
+ * @param {object} [options]
583
+ * @param {number} [options.mediaStartTime=0] - at what time in the audio the layer starts
584
+ * @param {numer} [options.duration=media.duration-options.mediaStartTime]
585
+ * @param {boolean} [options.muted=false]
586
+ * @param {number} [options.volume=1]
587
+ * @param {number} [options.playbackRate=1]
588
+ */
589
+ constructor (startTime, media, onload, options = {}) {
590
+ super(startTime, 0, options) // works with both Base and Visual
591
+ this._initialized = false
592
+ this._media = media
593
+ this._mediaStartTime = options.mediaStartTime || 0
594
+ applyOptions(options, this)
595
+
596
+ const load = () => {
597
+ // TODO: &amp;&amp; ?
598
+ if ((options.duration || (media.duration - this.mediaStartTime)) &lt; 0) {
599
+ throw new Error('Invalid options.duration or options.mediaStartTime')
600
+ }
601
+ this.duration = options.duration || (media.duration - this.mediaStartTime)
602
+ // onload will use `this`, and can't bind itself because it's before super()
603
+ onload &amp;&amp; onload.bind(this)(media, options)
604
+ }
605
+ if (media.readyState >= 2) {
606
+ // this frame's data is available now
607
+ load()
608
+ } else {
609
+ // when this frame's data is available
610
+ media.addEventListener('canplay', load)
611
+ }
612
+
613
+ subscribe(this, 'layer.attach', event => {
614
+ subscribe(event.movie, 'movie.seek', event => {
615
+ const time = event.movie.currentTime
616
+ if (time &lt; this.startTime || time >= this.startTime + this.duration) {
617
+ return
618
+ }
619
+ this.media.currentTime = time - this.startTime
620
+ })
621
+ // connect to audiocontext
622
+ this._source = event.movie.actx.createMediaElementSource(this.media)
623
+ this.source.connect(event.movie.actx.destination)
624
+ })
625
+ // TODO: on unattach?
626
+ subscribe(this, 'movie.audiodestinationupdate', event => {
627
+ // reset destination
628
+ this.source.disconnect()
629
+ this.source.connect(event.destination)
630
+ })
631
+ subscribe(this, 'layer.start', () => {
632
+ this.media.currentTime = this.mediaStartTime
633
+ this.media.play()
634
+ })
635
+ subscribe(this, 'layer.stop', () => {
636
+ this.media.pause()
637
+ })
638
+ }
639
+
640
+ _render (reltime) {
641
+ super._render(reltime)
642
+ // even interpolate here
643
+ // TODO: implement Issue: Create built-in audio node to support built-in audio nodes, as this does nothing rn
644
+ this.media.muted = val(this.muted, this, reltime)
645
+ this.media.volume = val(this.volume, this, reltime)
646
+ this.media.playbackRate = val(this.playbackRate, this, reltime)
647
+ }
648
+
649
+ /**
650
+ * The raw html media element
651
+ * @type HTMLMediaElement
652
+ */
653
+ get media () {
654
+ return this._media
655
+ }
656
+
657
+ /**
658
+ * The audio source node for the media
659
+ * @type MediaStreamAudioSourceNode
660
+ */
661
+ get source () {
662
+ return this._source
663
+ }
664
+
665
+ get startTime () {
666
+ return this._startTime
667
+ }
668
+
669
+ set startTime (val) {
670
+ this._startTime = val
671
+ if (this._initialized) {
672
+ const mediaProgress = this._movie.currentTime - this.startTime
673
+ this.media.currentTime = this.mediaStartTime + mediaProgress
674
+ }
675
+ }
676
+
677
+ set mediaStartTime (val) {
678
+ this._mediaStartTime = val
679
+ if (this._initialized) {
680
+ const mediaProgress = this._movie.currentTime - this.startTime
681
+ this.media.currentTime = mediaProgress + this.mediaStartTime
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Where in the media the layer starts at
687
+ * @type number
688
+ */
689
+ get mediaStartTime () {
690
+ return this._mediaStartTime
691
+ }
692
+ };
693
+ Media.prototype.getDefaultOptions = function () {
694
+ return {
695
+ ...superclass.prototype.getDefaultOptions(),
696
+ /**
697
+ * @name module:layer~Media#mediaStartTime
698
+ * @type number
699
+ * @desc Where in the media the layer starts at
700
+ */
701
+ mediaStartTime: 0,
702
+ /**
703
+ * @name module:layer~Media#duration
704
+ * @type number
705
+ */
706
+ duration: undefined, // important to include undefined keys, for applyOptions
707
+ /**
708
+ * @name module:layer~Media#muted
709
+ * @type boolean
710
+ */
711
+ muted: false,
712
+ /**
713
+ * @name module:layer~Media#volume
714
+ * @type number
715
+ */
716
+ volume: 1,
717
+ /**
718
+ * @name module:layer~Media#playbackRate
719
+ * @type number
720
+ * @todo &lt;strong>Implement&lt;/strong>
721
+ */
722
+ playbackRate: 1
723
+ }
724
+ }
725
+
726
+ return Media // custom mixin class
727
+ }
728
+
729
+ // use mixins instead of `extend`ing two classes (which doens't work); see below class def
730
+ /**
731
+ * @extends module:layer~Media
732
+ */
733
+ export class Video extends MediaMixin(Visual) {
734
+ /**
735
+ * Creates a new video layer
736
+ *
737
+ * @param {number} startTime
738
+ * @param {HTMLVideoElement} media
739
+ * @param {object} [options]
740
+ * @param {number} startTime
741
+ * @param {HTMLVideoElement} media
742
+ * @param {object} [options]
743
+ * @param {number} [options.mediaStartTime=0] - at what time in the audio the layer starts
744
+ * @param {numer} [options.duration=media.duration-options.mediaStartTime]
745
+ * @param {boolean} [options.muted=false]
746
+ * @param {number} [options.volume=1]
747
+ * @param {number} [options.speed=1] - the audio's playerback rate
748
+ * @param {number} [options.mediaStartTime=0] - at what time in the video the layer starts
749
+ * @param {numer} [options.duration=media.duration-options.mediaStartTime]
750
+ * @param {number} [options.clipX=0] - video source x
751
+ * @param {number} [options.clipY=0] - video source y
752
+ * @param {number} [options.clipWidth=0] - video destination width
753
+ * @param {number} [options.clipHeight=0] - video destination height
754
+ * @param {number} [options.mediaX=0] - video offset relative to the layer
755
+ * @param {number} [options.mediaY=0] - video offset relative to the layer
756
+ */
757
+ constructor (startTime, media, options = {}) {
758
+ // fill in the zeros once loaded
759
+ super(startTime, media, function () {
760
+ this.width = this.mediaWidth = options.width || media.videoWidth
761
+ this.height = this.mediaHeight = options.height || media.videoHeight
762
+ this.clipWidth = options.clipWidth || media.videoWidth
763
+ this.clipHeight = options.clipHeight || media.videoHeight
764
+ }, options)
765
+ // clipX... => how much to show of this.media
766
+ // mediaX... => how to project this.media onto the canvas
767
+ applyOptions(options, this)
768
+ if (this.duration === undefined) {
769
+ this.duration = media.duration - this.mediaStartTime
770
+ }
771
+ }
772
+
773
+ _doRender (reltime) {
774
+ super._doRender()
775
+ this.cctx.drawImage(this.media,
776
+ val(this.clipX, this, reltime), val(this.clipY, this, reltime),
777
+ val(this.clipWidth, this, reltime), val(this.clipHeight, this, reltime),
778
+ val(this.mediaX, this, reltime), val(this.mediaY, this, reltime), // relative to layer
779
+ val(this.mediaWidth, this, reltime), val(this.mediaHeight, this, reltime))
780
+ }
781
+ }
782
+ Video.prototype.getDefaultOptions = function () {
783
+ return {
784
+ ...Object.getPrototypeOf(this).getDefaultOptions(), // let's not call MediaMixin again
785
+ /**
786
+ * @name module:layer.Video#clipX
787
+ * @type number
788
+ * @desc Video source x
789
+ */
790
+ clipX: 0,
791
+ /**
792
+ * @name module:layer.Video#clipY
793
+ * @type number
794
+ * @desc Video source y
795
+ */
796
+ clipY: 0,
797
+ /**
798
+ * @name module:layer.Video#mediaX
799
+ * @type number
800
+ * @desc Video offset relative to layer
801
+ */
802
+ mediaX: 0,
803
+ /**
804
+ * @name module:layer.Video#mediaY
805
+ * @type number
806
+ * @desc Video offset relative to layer
807
+ */
808
+ mediaY: 0,
809
+ /**
810
+ * @name module:layer.Video#mediaWidth
811
+ * @type number
812
+ * @desc Video destination width
813
+ */
814
+ mediaWidth: undefined,
815
+ /**
816
+ * @name module:layer.Video#mediaHeight
817
+ * @type number
818
+ * @desc Video destination height
819
+ */
820
+ mediaHeight: undefined
821
+ }
822
+ }
823
+
824
+ /**
825
+ * @extends module:layer~Media
826
+ */
827
+ export class Audio extends MediaMixin(Base) {
828
+ /**
829
+ * Creates an audio layer
830
+ *
831
+ * @param {number} startTime
832
+ * @param {HTMLAudioElement} media
833
+ * @param {object} [options]
834
+ * @param {number} startTime
835
+ * @param {HTMLVideoElement} media
836
+ * @param {object} [options]
837
+ * @param {number} [options.mediaStartTime=0] - at what time in the audio the layer starts
838
+ * @param {numer} [options.duration=media.duration-options.mediaStartTime]
839
+ * @param {boolean} [options.muted=false]
840
+ * @param {number} [options.volume=1]
841
+ * @param {number} [options.speed=1] - the audio's playerback rate
842
+ */
843
+ constructor (startTime, media, options = {}) {
844
+ // fill in the zero once loaded, no width or height (will raise error)
845
+ super(startTime, media, null, options)
846
+ applyOptions(options, this)
847
+ if (this.duration === undefined) {
848
+ this.duration = media.duration - this.mediaStartTime
849
+ }
850
+ }
851
+ }
852
+ Audio.prototype.getDefaultOptions = function () {
853
+ return {
854
+ ...Object.getPrototypeOf(this).getDefaultOptions(), // let's not call MediaMixin again
855
+ /**
856
+ * @name module:layer.Audio#mediaStartTime
857
+ * @type number
858
+ * @desc Where in the media to start playing when the layer starts
859
+ */
860
+ mediaStartTime: 0,
861
+ duration: undefined
862
+ }
863
+ }
864
+ </code></pre>
865
+ </article>
866
+ </section>
867
+
868
+
869
+
870
+
871
+
872
+
873
+ </div>
874
+
875
+ <br class="clear">
876
+
877
+ <footer>
878
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Sun Oct 13 2019 15:38:43 GMT-0400 (Eastern Daylight Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
879
+ </footer>
880
+
881
+ <script>prettyPrint();</script>
882
+ <script src="scripts/polyfill.js"></script>
883
+ <script src="scripts/linenumber.js"></script>
884
+
885
+
886
+
887
+ </body>
888
+ </html>