etro 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/CONTRIBUTING.md +23 -20
- package/README.md +3 -2
- package/dist/custom-array.d.ts +10 -0
- package/dist/effect/base.d.ts +10 -1
- package/dist/effect/shader.d.ts +11 -1
- package/dist/effect/stack.d.ts +6 -2
- package/dist/etro-cjs.js +1156 -575
- package/dist/etro-iife.js +1156 -575
- package/dist/event.d.ts +10 -5
- package/dist/layer/audio-source.d.ts +9 -4
- package/dist/layer/audio.d.ts +15 -2
- package/dist/layer/base.d.ts +49 -3
- package/dist/layer/image.d.ts +15 -1
- package/dist/layer/text.d.ts +3 -0
- package/dist/layer/video.d.ts +13 -1
- package/dist/layer/visual.d.ts +6 -2
- package/dist/movie/effects.d.ts +6 -0
- package/dist/movie/index.d.ts +1 -0
- package/dist/movie/layers.d.ts +6 -0
- package/dist/movie/movie.d.ts +260 -0
- package/dist/object.d.ts +9 -6
- package/dist/util.d.ts +4 -10
- package/eslint.conf.js +2 -2
- package/karma.conf.js +4 -7
- package/package.json +8 -7
- package/src/custom-array.ts +43 -0
- package/src/effect/base.ts +23 -22
- package/src/effect/gaussian-blur.ts +11 -6
- package/src/effect/pixelate.ts +3 -3
- package/src/effect/shader.ts +33 -27
- package/src/effect/stack.ts +43 -30
- package/src/effect/transform.ts +14 -7
- package/src/event.ts +111 -21
- package/src/layer/audio-source.ts +60 -20
- package/src/layer/audio.ts +25 -3
- package/src/layer/base.ts +79 -26
- package/src/layer/image.ts +26 -2
- package/src/layer/text.ts +7 -0
- package/src/layer/video.ts +31 -4
- package/src/layer/visual-source.ts +43 -1
- package/src/layer/visual.ts +50 -28
- package/src/movie/effects.ts +26 -0
- package/src/movie/index.ts +1 -0
- package/src/movie/layers.ts +26 -0
- package/src/movie/movie.ts +855 -0
- package/src/object.ts +9 -6
- package/src/util.ts +68 -89
- package/dist/movie.d.ts +0 -201
- package/src/movie.ts +0 -744
- /package/scripts/{gen-effect-samples.html → effect/gen-effect-samples.html} +0 -0
- /package/scripts/{save-effect-samples.js → effect/save-effect-samples.js} +0 -0
package/src/layer/video.ts
CHANGED
|
@@ -2,14 +2,41 @@ import { Visual } from './visual'
|
|
|
2
2
|
import { VisualSourceOptions, VisualSourceMixin } from './visual-source'
|
|
3
3
|
import { AudioSourceOptions, AudioSourceMixin } from './audio-source'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface VideoOptions extends Omit<AudioSourceOptions & VisualSourceOptions, 'duration'|'source'> {
|
|
6
|
+
duration?: number
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The raw html `<video>` element
|
|
10
|
+
*/
|
|
11
|
+
source: string | HTMLVideoElement
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
// Use mixins instead of `extend`ing two classes (which isn't supported by
|
|
8
|
-
// JavaScript).
|
|
9
14
|
/**
|
|
15
|
+
* Layer for an HTML video element
|
|
10
16
|
* @extends AudioSource
|
|
11
17
|
* @extends VisualSource
|
|
12
18
|
*/
|
|
13
|
-
class Video extends AudioSourceMixin(VisualSourceMixin(Visual)) {
|
|
19
|
+
class Video extends AudioSourceMixin(VisualSourceMixin(Visual)) {
|
|
20
|
+
/**
|
|
21
|
+
* The raw html `<video>` element
|
|
22
|
+
*/
|
|
23
|
+
source: HTMLVideoElement
|
|
24
|
+
|
|
25
|
+
constructor (options: VideoOptions) {
|
|
26
|
+
if (typeof (options.source) === 'string') {
|
|
27
|
+
const video = document.createElement('video')
|
|
28
|
+
video.src = options.source
|
|
29
|
+
options.source = video
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
super({
|
|
33
|
+
...options,
|
|
34
|
+
|
|
35
|
+
// Set a default duration so that the super constructor doesn't throw an
|
|
36
|
+
// error
|
|
37
|
+
duration: options.duration ?? 0
|
|
38
|
+
} as (AudioSourceOptions & VisualSourceOptions))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
14
41
|
|
|
15
42
|
export { Video, VideoOptions }
|
|
@@ -75,10 +75,40 @@ function VisualSourceMixin<OptionsSuperclass extends VisualOptions> (superclass:
|
|
|
75
75
|
destHeight: Dynamic<number>
|
|
76
76
|
|
|
77
77
|
constructor (options: MixedVisualSourceOptions) {
|
|
78
|
+
if (!options.source) {
|
|
79
|
+
throw new Error('Property "source" is required in options')
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
super(options)
|
|
79
83
|
applyOptions(options, this)
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
async whenReady (): Promise<void> {
|
|
87
|
+
await super.whenReady()
|
|
88
|
+
|
|
89
|
+
await new Promise<void>(resolve => {
|
|
90
|
+
if (this.source instanceof HTMLImageElement) {
|
|
91
|
+
// The source is an image; wait for it to load
|
|
92
|
+
if (this.source.complete) {
|
|
93
|
+
resolve()
|
|
94
|
+
} else {
|
|
95
|
+
this.source.addEventListener('load', () => {
|
|
96
|
+
resolve()
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// The source is a video; wait for the first frame to load
|
|
101
|
+
if (this.source.readyState === 4) {
|
|
102
|
+
resolve()
|
|
103
|
+
} else {
|
|
104
|
+
this.source.addEventListener('canplaythrough', () => {
|
|
105
|
+
resolve()
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
doRender () {
|
|
83
113
|
// Clear/fill background
|
|
84
114
|
super.doRender()
|
|
@@ -86,7 +116,7 @@ function VisualSourceMixin<OptionsSuperclass extends VisualOptions> (superclass:
|
|
|
86
116
|
/*
|
|
87
117
|
* Source dimensions crop the image. Dest dimensions set the size that
|
|
88
118
|
* the image will be rendered at *on the layer*. Note that this is
|
|
89
|
-
* different
|
|
119
|
+
* different from the layer dimensions (`this.width` and `this.height`).
|
|
90
120
|
* The main reason this distinction exists is so that an image layer can
|
|
91
121
|
* be rotated without being cropped (see iss #46).
|
|
92
122
|
*/
|
|
@@ -100,6 +130,18 @@ function VisualSourceMixin<OptionsSuperclass extends VisualOptions> (superclass:
|
|
|
100
130
|
)
|
|
101
131
|
}
|
|
102
132
|
|
|
133
|
+
get ready (): boolean {
|
|
134
|
+
// Typescript doesn't support `super.ready` when targeting es5
|
|
135
|
+
const superReady = Object.getOwnPropertyDescriptor(superclass.prototype, 'ready').get.call(this)
|
|
136
|
+
const sourceReady = this.source instanceof HTMLImageElement
|
|
137
|
+
? this.source.complete
|
|
138
|
+
: this.source.readyState === 4
|
|
139
|
+
return superReady && sourceReady
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
144
|
+
*/
|
|
103
145
|
getDefaultOptions (): MixedVisualSourceOptions {
|
|
104
146
|
return {
|
|
105
147
|
...superclass.prototype.getDefaultOptions(),
|
package/src/layer/visual.ts
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
|
+
import { CustomArray, CustomArrayListener } from '../custom-array'
|
|
1
2
|
import { Dynamic, val, applyOptions, Color } from '../util'
|
|
2
3
|
import { Base, BaseOptions } from './base'
|
|
3
4
|
import { Visual as VisualEffect } from '../effect/visual'
|
|
4
5
|
|
|
6
|
+
// eslint-disable-next-line no-use-before-define
|
|
7
|
+
class VisualEffectsListener extends CustomArrayListener<VisualEffect> {
|
|
8
|
+
// eslint-disable-next-line no-use-before-define
|
|
9
|
+
private _layer: Visual
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line no-use-before-define
|
|
12
|
+
constructor (layer: Visual) {
|
|
13
|
+
super()
|
|
14
|
+
this._layer = layer
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
onAdd (effect: VisualEffect) {
|
|
18
|
+
effect.tryAttach(this._layer)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onRemove (effect: VisualEffect) {
|
|
22
|
+
effect.tryDetach()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class VisualEffects extends CustomArray<VisualEffect> {
|
|
27
|
+
// eslint-disable-next-line no-use-before-define
|
|
28
|
+
constructor (target: VisualEffect[], layer: Visual) {
|
|
29
|
+
super(target, new VisualEffectsListener(layer))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
5
33
|
interface VisualOptions extends BaseOptions {
|
|
6
34
|
x?: Dynamic<number>
|
|
7
35
|
y?: Dynamic<number>
|
|
@@ -43,40 +71,22 @@ class Visual extends Base {
|
|
|
43
71
|
// readonly because it's a proxy
|
|
44
72
|
readonly effects: VisualEffect[]
|
|
45
73
|
|
|
46
|
-
private _effectsBack: VisualEffect[]
|
|
47
|
-
|
|
48
74
|
/**
|
|
49
75
|
* Creates a visual layer
|
|
50
76
|
*/
|
|
51
77
|
constructor (options: VisualOptions) {
|
|
52
78
|
super(options)
|
|
53
|
-
|
|
54
|
-
// be extraneous options.
|
|
79
|
+
|
|
55
80
|
applyOptions(options, this)
|
|
56
81
|
|
|
57
82
|
this.canvas = document.createElement('canvas')
|
|
58
83
|
this.cctx = this.canvas.getContext('2d')
|
|
84
|
+
this.effects = new VisualEffects([], this)
|
|
85
|
+
}
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const value = target[property]
|
|
64
|
-
value.detach()
|
|
65
|
-
delete target[property]
|
|
66
|
-
return true
|
|
67
|
-
},
|
|
68
|
-
set: (target, property, value) => {
|
|
69
|
-
if (!isNaN(Number(property))) {
|
|
70
|
-
// The property is a number (index)
|
|
71
|
-
if (target[property])
|
|
72
|
-
target[property].detach()
|
|
73
|
-
|
|
74
|
-
value.attach(this)
|
|
75
|
-
}
|
|
76
|
-
target[property] = value
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
})
|
|
87
|
+
async whenReady (): Promise<void> {
|
|
88
|
+
await super.whenReady()
|
|
89
|
+
await Promise.all(this.effects.map(effect => effect.whenReady()))
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
/**
|
|
@@ -86,8 +96,9 @@ class Visual extends Base {
|
|
|
86
96
|
// Prevent empty canvas errors if the width or height is 0
|
|
87
97
|
const width = val(this, 'width', this.currentTime)
|
|
88
98
|
const height = val(this, 'height', this.currentTime)
|
|
89
|
-
if (width === 0 || height === 0)
|
|
99
|
+
if (width === 0 || height === 0) {
|
|
90
100
|
return
|
|
101
|
+
}
|
|
91
102
|
|
|
92
103
|
this.beginRender()
|
|
93
104
|
this.doRender()
|
|
@@ -122,8 +133,9 @@ class Visual extends Base {
|
|
|
122
133
|
endRender (): void {
|
|
123
134
|
const w = val(this, 'width', this.currentTime) || val(this.movie, 'width', this.movie.currentTime)
|
|
124
135
|
const h = val(this, 'height', this.currentTime) || val(this.movie, 'height', this.movie.currentTime)
|
|
125
|
-
if (w * h > 0)
|
|
136
|
+
if (w * h > 0) {
|
|
126
137
|
this._applyEffects()
|
|
138
|
+
}
|
|
127
139
|
|
|
128
140
|
// else InvalidStateError for drawing zero-area image in some effects, right?
|
|
129
141
|
}
|
|
@@ -131,14 +143,15 @@ class Visual extends Base {
|
|
|
131
143
|
_applyEffects (): void {
|
|
132
144
|
for (let i = 0; i < this.effects.length; i++) {
|
|
133
145
|
const effect = this.effects[i]
|
|
134
|
-
if (effect && effect.enabled)
|
|
146
|
+
if (effect && effect.enabled) {
|
|
135
147
|
// Pass relative time
|
|
136
148
|
effect.apply(this, this.movie.currentTime - this.startTime)
|
|
149
|
+
}
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
/**
|
|
141
|
-
*
|
|
154
|
+
* Convenience method for <code>effects.push()</code>
|
|
142
155
|
* @param effect
|
|
143
156
|
* @return the layer (for chaining)
|
|
144
157
|
*/
|
|
@@ -146,6 +159,15 @@ class Visual extends Base {
|
|
|
146
159
|
this.effects.push(effect); return this
|
|
147
160
|
}
|
|
148
161
|
|
|
162
|
+
get ready (): boolean {
|
|
163
|
+
// Typescript doesn't support `super.ready` when targeting es5
|
|
164
|
+
const superReady = Object.getOwnPropertyDescriptor(Base.prototype, 'ready').get.call(this)
|
|
165
|
+
return superReady && this.effects.every(effect => effect.ready)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated See {@link https://github.com/etro-js/etro/issues/131}
|
|
170
|
+
*/
|
|
149
171
|
getDefaultOptions (): VisualOptions {
|
|
150
172
|
return {
|
|
151
173
|
...Base.prototype.getDefaultOptions(),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CustomArray, CustomArrayListener } from '../custom-array'
|
|
2
|
+
import { Visual as VisualEffect } from '../effect/index'
|
|
3
|
+
import { Movie } from './movie'
|
|
4
|
+
|
|
5
|
+
class MovieEffectsListener extends CustomArrayListener<VisualEffect> {
|
|
6
|
+
private _movie: Movie
|
|
7
|
+
|
|
8
|
+
constructor (movie: Movie) {
|
|
9
|
+
super()
|
|
10
|
+
this._movie = movie
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
onAdd (effect: VisualEffect) {
|
|
14
|
+
effect.tryAttach(this._movie)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
onRemove (effect: VisualEffect) {
|
|
18
|
+
effect.tryDetach()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class MovieEffects extends CustomArray<VisualEffect> {
|
|
23
|
+
constructor (target: VisualEffect[], movie: Movie) {
|
|
24
|
+
super(target, new MovieEffectsListener(movie))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './movie'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CustomArray, CustomArrayListener } from '../custom-array'
|
|
2
|
+
import { Base as BaseLayer } from '../layer/index'
|
|
3
|
+
import { Movie } from './movie'
|
|
4
|
+
|
|
5
|
+
class MovieLayersListener extends CustomArrayListener<BaseLayer> {
|
|
6
|
+
private _movie: Movie
|
|
7
|
+
|
|
8
|
+
constructor (movie: Movie) {
|
|
9
|
+
super()
|
|
10
|
+
this._movie = movie
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
onAdd (layer: BaseLayer) {
|
|
14
|
+
layer.tryAttach(this._movie)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
onRemove (layer: BaseLayer) {
|
|
18
|
+
layer.tryDetach()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class MovieLayers extends CustomArray<BaseLayer> {
|
|
23
|
+
constructor (target: BaseLayer[], movie: Movie) {
|
|
24
|
+
super(target, new MovieLayersListener(movie))
|
|
25
|
+
}
|
|
26
|
+
}
|