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.
- package/.env +2 -0
- package/.github/workflows/nodejs.yml +27 -0
- package/CHANGELOG.md +109 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/CONTRIBUTING.md +155 -0
- package/LICENSE +674 -0
- package/README.md +57 -0
- package/dist/etro.js +3397 -0
- package/docs/effect.js.html +1215 -0
- package/docs/event.js.html +145 -0
- package/docs/index.html +81 -0
- package/docs/index.js.html +92 -0
- package/docs/layer.js.html +888 -0
- package/docs/module-effect-GaussianBlurComponent.html +345 -0
- package/docs/module-effect.Brightness.html +339 -0
- package/docs/module-effect.Channels.html +319 -0
- package/docs/module-effect.ChromaKey.html +611 -0
- package/docs/module-effect.Contrast.html +339 -0
- package/docs/module-effect.EllipticalMask.html +200 -0
- package/docs/module-effect.GaussianBlur.html +202 -0
- package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
- package/docs/module-effect.GaussianBlurVertical.html +242 -0
- package/docs/module-effect.Pixelate.html +330 -0
- package/docs/module-effect.Shader.html +1227 -0
- package/docs/module-effect.Stack.html +406 -0
- package/docs/module-effect.Transform.Matrix.html +193 -0
- package/docs/module-effect.Transform.html +1174 -0
- package/docs/module-effect.html +148 -0
- package/docs/module-event.html +473 -0
- package/docs/module-index.html +186 -0
- package/docs/module-layer-Media.html +1116 -0
- package/docs/module-layer-MediaMixin.html +164 -0
- package/docs/module-layer.Audio.html +1188 -0
- package/docs/module-layer.Base.html +629 -0
- package/docs/module-layer.Image.html +1421 -0
- package/docs/module-layer.Text.html +1731 -0
- package/docs/module-layer.Video.html +1938 -0
- package/docs/module-layer.Visual.html +1698 -0
- package/docs/module-layer.html +137 -0
- package/docs/module-movie.html +3118 -0
- package/docs/module-util.Color.html +702 -0
- package/docs/module-util.Font.html +395 -0
- package/docs/module-util.html +845 -0
- package/docs/movie.js.html +689 -0
- package/docs/scripts/collapse.js +20 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +83 -0
- package/docs/styles/jsdoc.css +671 -0
- package/docs/styles/prettify.css +79 -0
- package/docs/util.js.html +503 -0
- package/eslint.conf.js +28 -0
- package/eslint.test-conf.js +4 -0
- package/examples/application/readme-screenshot.html +86 -0
- package/examples/application/video-player.html +131 -0
- package/examples/application/webcam.html +28 -0
- package/examples/introduction/audio.html +52 -0
- package/examples/introduction/effects.html +56 -0
- package/examples/introduction/export.html +70 -0
- package/examples/introduction/functions.html +35 -0
- package/examples/introduction/hello-world1.html +33 -0
- package/examples/introduction/hello-world2.html +32 -0
- package/examples/introduction/keyframes.html +67 -0
- package/examples/introduction/media.html +55 -0
- package/examples/introduction/text.html +27 -0
- package/jsdoc.conf.json +3 -0
- package/karma.conf.js +60 -0
- package/package.json +63 -0
- package/private-todo.txt +70 -0
- package/rename-file.sh +18 -0
- package/rename-versions.sh +14 -0
- package/rename.sh +22 -0
- package/rollup.config.js +31 -0
- package/screenshots/2019-08-17_0.png +0 -0
- package/scripts/gen-effect-samples.html +99 -0
- package/scripts/save-effect-samples.js +43 -0
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.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 +352 -0
- package/spec/event.spec.js +25 -0
- package/spec/layer.spec.js +128 -0
- package/spec/movie.spec.js +154 -0
- package/spec/util.spec.js +285 -0
- package/src/effect.js +1265 -0
- package/src/event.js +78 -0
- package/src/index.js +23 -0
- package/src/layer.js +875 -0
- package/src/movie.js +636 -0
- 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 <code>render</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 <code>null</code>
|
|
150
|
+
* for a transparent background
|
|
151
|
+
* @param {object} [options.border=null] - the layer's outline, or <code>null</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; <code>1</cod> for full opacity
|
|
155
|
+
* and <code>0</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 & 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 && 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 < 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 <code>effects.push()</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 <code>null</code> for transparency
|
|
295
|
+
*/
|
|
296
|
+
background: null,
|
|
297
|
+
/**
|
|
298
|
+
* @name module:layer.Visual#border
|
|
299
|
+
* @type string
|
|
300
|
+
* @desc The css border style, or <code>null</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 <code>null</code>
|
|
326
|
+
* for a transparent background
|
|
327
|
+
* @param {object} [options.border=null] - the layer's outline, or <code>null</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; <code>1</cod> for full opacity
|
|
331
|
+
* and <code>0</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 [<code>CanvasRenderingContext2D#textAlign</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 [<code>CanvasRenderingContext2D#textBaseline</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 [<code>CanvasRenderingContext2D#direction</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 <code>null</code>
|
|
473
|
+
* for transparency
|
|
474
|
+
* @param {object} [options.border=null] - the layer"s outline, or <code>null</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; <code>1</cod> for full opacity
|
|
478
|
+
* and <code>0</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 <code>undefined</code> to fill the entire layer
|
|
482
|
+
* @param {number} [options.clipHeight=undefined] - image source height, or <code>undefined</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 & 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 <code>undefined</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 <code>undefined</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 && 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: && ?
|
|
598
|
+
if ((options.duration || (media.duration - this.mediaStartTime)) < 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 && 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 < 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 <strong>Implement</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>
|