etro 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/nodejs.yml +1 -1
- package/CHANGELOG.md +27 -0
- package/CONTRIBUTING.md +13 -26
- package/README.md +8 -15
- package/dist/effect/base.d.ts +5 -5
- package/dist/effect/visual.d.ts +11 -0
- package/dist/etro-cjs.js +84 -53
- package/dist/etro-iife.js +84 -53
- package/dist/layer/image.d.ts +2 -2
- package/dist/layer/text.d.ts +3 -3
- package/dist/layer/visual-source.d.ts +18 -3
- package/dist/layer/visual.d.ts +5 -5
- package/dist/movie.d.ts +13 -4
- package/dist/object.d.ts +5 -1
- package/dist/util.d.ts +2 -0
- package/eslint.conf.js +2 -0
- package/eslint.test-conf.js +1 -2
- package/karma.conf.js +17 -8
- package/package.json +19 -19
- package/scripts/gen-effect-samples.html +24 -0
- package/scripts/save-effect-samples.js +1 -1
- package/src/effect/base.ts +6 -6
- package/src/effect/stack.ts +2 -2
- package/src/effect/transform.ts +2 -2
- package/src/effect/visual.ts +16 -1
- package/src/layer/audio-source.ts +4 -1
- package/src/layer/base.ts +3 -2
- package/src/layer/image.ts +3 -3
- package/src/layer/text.ts +4 -4
- package/src/layer/visual-source.ts +27 -7
- package/src/layer/visual.ts +7 -7
- package/src/movie.ts +55 -37
- package/src/object.ts +5 -1
- package/src/util.ts +2 -0
- package/tsconfig.json +3 -1
- package/examples/application/readme-screenshot.html +0 -85
- package/examples/application/video-player.html +0 -130
- package/examples/application/webcam.html +0 -28
- package/examples/introduction/audio.html +0 -64
- package/examples/introduction/effects.html +0 -79
- package/examples/introduction/export.html +0 -83
- package/examples/introduction/functions.html +0 -37
- package/examples/introduction/hello-world1.html +0 -37
- package/examples/introduction/hello-world2.html +0 -32
- package/examples/introduction/keyframes.html +0 -79
- package/examples/introduction/media.html +0 -63
- package/examples/introduction/text.html +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "etro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "An extendable video-editing framework for the browser and Node",
|
|
5
5
|
"browser": "dist/etro-cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,39 +10,39 @@
|
|
|
10
10
|
"test": "spec"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@
|
|
14
|
-
"@
|
|
15
|
-
"@typescript-eslint/
|
|
13
|
+
"@rollup/plugin-eslint": "^8.0.2",
|
|
14
|
+
"@types/jest": "^29.0.0",
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
|
16
|
+
"@typescript-eslint/parser": "^5.30.7",
|
|
16
17
|
"docdash": "^1.1.1",
|
|
17
18
|
"ecstatic": ">=4.1.3",
|
|
18
|
-
"eslint": "^
|
|
19
|
-
"eslint-config-standard": "^
|
|
20
|
-
"eslint-plugin-html": "^
|
|
21
|
-
"eslint-plugin-import": "^2.
|
|
22
|
-
"eslint-plugin-node": "^
|
|
23
|
-
"eslint-plugin-promise": "^
|
|
24
|
-
"eslint-plugin-standard": "^
|
|
19
|
+
"eslint": "^8.20.0",
|
|
20
|
+
"eslint-config-standard": "^17.0.0",
|
|
21
|
+
"eslint-plugin-html": "^7.0.0",
|
|
22
|
+
"eslint-plugin-import": "^2.26.0",
|
|
23
|
+
"eslint-plugin-node": "^11.1.0",
|
|
24
|
+
"eslint-plugin-promise": "^6.0.0",
|
|
25
|
+
"eslint-plugin-standard": "^5.0.0",
|
|
25
26
|
"ev": "0.0.7",
|
|
26
|
-
"http-server": "^
|
|
27
|
+
"http-server": "^14.1.1",
|
|
27
28
|
"jasmine": "^3.4.0",
|
|
29
|
+
"jasmine-ts": "^0.4.0",
|
|
28
30
|
"karma": "^6.1.1",
|
|
29
|
-
"karma-es6-shim": "^1.0.0",
|
|
30
31
|
"karma-firefox-launcher": "^2.1.2",
|
|
31
32
|
"karma-jasmine": "^2.0.1",
|
|
32
|
-
"karma-requirejs": "^1.1.0",
|
|
33
33
|
"karma-super-dots-reporter": "^0.2.0",
|
|
34
|
+
"karma-typescript": "^5.5.3",
|
|
34
35
|
"keep-a-changelog": "^0.10.4",
|
|
35
36
|
"puppeteer": "^2.0.0",
|
|
36
37
|
"resemblejs": "^4.1.0",
|
|
37
38
|
"rollup": "^1.19.4",
|
|
38
39
|
"rollup-plugin-cleaner": "^1.0.0",
|
|
39
|
-
"rollup-plugin-eslint": "^7.0.0",
|
|
40
40
|
"rollup-plugin-node-resolve": "^5.2.0",
|
|
41
41
|
"rollup-plugin-typescript2": "^0.29.0",
|
|
42
42
|
"rollup-plugin-uglify-es": "^0.0.1",
|
|
43
|
-
"shipjs": "0.
|
|
44
|
-
"typedoc": "^0.
|
|
45
|
-
"typescript": "^4.
|
|
43
|
+
"shipjs": "^0.24.4",
|
|
44
|
+
"typedoc": "^0.23.8",
|
|
45
|
+
"typescript": "^4.7.4"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "rollup -c",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"effects": "node scripts/save-effect-samples.js",
|
|
52
52
|
"lint": "npm run --silent lint:main && npm run --silent lint:test && npm run --silent lint:examples",
|
|
53
53
|
"lint:main": "eslint -c eslint.typescript-conf.js --ext .ts src",
|
|
54
|
-
"lint:test": "eslint -c eslint.test-conf.js spec",
|
|
54
|
+
"lint:test": "eslint -c eslint.test-conf.js --ext .ts spec",
|
|
55
55
|
"lint:examples": "eslint -c eslint.example-conf.js --ext .html examples",
|
|
56
56
|
"start": "http-server",
|
|
57
57
|
"test": "karma start",
|
|
@@ -76,10 +76,34 @@
|
|
|
76
76
|
save(original, 'original.png')
|
|
77
77
|
|
|
78
78
|
const samples = {
|
|
79
|
+
'brightness.png': new etro.effect.Brightness({
|
|
80
|
+
brightness: -100
|
|
81
|
+
}),
|
|
82
|
+
'contrast.png': new etro.effect.Contrast({
|
|
83
|
+
contrast: 0.5
|
|
84
|
+
}),
|
|
85
|
+
'channels.png': new etro.effect.Channels({
|
|
86
|
+
factors: {
|
|
87
|
+
r: 0.25,
|
|
88
|
+
g: 0.5,
|
|
89
|
+
b: 0.75
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
'chroma-key.png': new etro.effect.ChromaKey({
|
|
93
|
+
target: etro.parseColor('green'),
|
|
94
|
+
threshold: 100
|
|
95
|
+
}),
|
|
79
96
|
'gaussian-blur-horizontal.png': new etro.effect.GaussianBlurHorizontal({ radius: 5 }),
|
|
80
97
|
'gaussian-blur-vertical.png': new etro.effect.GaussianBlurVertical({ radius: 5 }),
|
|
81
98
|
'grayscale.png': new etro.effect.Grayscale(),
|
|
82
99
|
'pixelate.png': new etro.effect.Pixelate({ pixelSize: 3 }),
|
|
100
|
+
'shader.png': new etro.effect.Shader(),
|
|
101
|
+
'stack.png': new etro.effect.Stack({
|
|
102
|
+
effects: [
|
|
103
|
+
new etro.effect.Brightness({ brightness: -100 }),
|
|
104
|
+
new etro.effect.Contrast({ contrast: 0.5 })
|
|
105
|
+
]
|
|
106
|
+
}),
|
|
83
107
|
'transform/translate.png': new etro.effect.Transform({
|
|
84
108
|
matrix: new etro.effect.Transform.Matrix().translate(-3, 5)
|
|
85
109
|
}),
|
|
@@ -39,7 +39,7 @@ function createDirs(filePath) {
|
|
|
39
39
|
// remove prefix and save to png
|
|
40
40
|
const buffer = Buffer.from(item.data.replace(/^data:image\/png;base64,/, ''), 'base64')
|
|
41
41
|
console.log(`writing ${item.path} ...`)
|
|
42
|
-
const path = projectDir + '/spec/assets/effect/' + item.path
|
|
42
|
+
const path = projectDir + '/spec/integration/assets/effect/' + item.path
|
|
43
43
|
createDirs(path)
|
|
44
44
|
fs.writeFileSync(path, buffer)
|
|
45
45
|
})
|
package/src/effect/base.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { watchPublic } from '../util'
|
|
2
2
|
import { publish, subscribe } from '../event'
|
|
3
3
|
import { Movie } from '../movie'
|
|
4
|
-
import {
|
|
4
|
+
import { Base as BaseLayer } from '../layer/index'
|
|
5
5
|
import BaseObject from '../object'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -14,7 +14,7 @@ export class Base implements BaseObject {
|
|
|
14
14
|
|
|
15
15
|
enabled: boolean
|
|
16
16
|
|
|
17
|
-
private _target: Movie |
|
|
17
|
+
private _target: Movie | BaseLayer
|
|
18
18
|
/**
|
|
19
19
|
* The number of times this effect has been attached to a target minus the
|
|
20
20
|
* number of times it's been detached. (Used for the target's array proxy with
|
|
@@ -45,14 +45,14 @@ export class Base implements BaseObject {
|
|
|
45
45
|
* Attaches this effect to `target` if not already attached.
|
|
46
46
|
* @ignore
|
|
47
47
|
*/
|
|
48
|
-
tryAttach (target: Movie |
|
|
48
|
+
tryAttach (target: Movie | BaseLayer): void {
|
|
49
49
|
if (this._occurrenceCount === 0)
|
|
50
50
|
this.attach(target)
|
|
51
51
|
|
|
52
52
|
this._occurrenceCount++
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
attach (movie: Movie |
|
|
55
|
+
attach (movie: Movie | BaseLayer): void {
|
|
56
56
|
this._target = movie
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -87,7 +87,7 @@ export class Base implements BaseObject {
|
|
|
87
87
|
* (will soon be replaced with an instance getter)
|
|
88
88
|
* @abstract
|
|
89
89
|
*/
|
|
90
|
-
apply (target: Movie |
|
|
90
|
+
apply (target: Movie | BaseLayer, reltime: number): void {} // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* The current time of the target
|
|
@@ -96,7 +96,7 @@ export class Base implements BaseObject {
|
|
|
96
96
|
return this._target ? this._target.currentTime : undefined
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
get parent (): Movie |
|
|
99
|
+
get parent (): Movie | BaseLayer {
|
|
100
100
|
return this._target
|
|
101
101
|
}
|
|
102
102
|
|
package/src/effect/stack.ts
CHANGED
|
@@ -21,13 +21,13 @@ export class Stack extends Visual {
|
|
|
21
21
|
this._effectsBack = []
|
|
22
22
|
// TODO: Throw 'change' events in handlers
|
|
23
23
|
this.effects = new Proxy(this._effectsBack, {
|
|
24
|
-
deleteProperty: function (target: Visual[], property:
|
|
24
|
+
deleteProperty: function (target: Visual[], property: string | symbol): boolean {
|
|
25
25
|
const value = target[property]
|
|
26
26
|
value.detach() // Detach effect from movie
|
|
27
27
|
delete target[property]
|
|
28
28
|
return true
|
|
29
29
|
},
|
|
30
|
-
set: function (target: Visual[], property:
|
|
30
|
+
set: function (target: Visual[], property: string | symbol, value: Visual): boolean {
|
|
31
31
|
// TODO: make sure type check works
|
|
32
32
|
if (!isNaN(Number(property))) { // if property is a number (index)
|
|
33
33
|
if (target[property])
|
package/src/effect/transform.ts
CHANGED
|
@@ -15,9 +15,9 @@ export interface TransformOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
class Transform extends Visual {
|
|
17
17
|
/** Matrix that determines how to transform the target */
|
|
18
|
-
matrix: Dynamic<Transform.Matrix>
|
|
18
|
+
matrix: Dynamic<Transform.Matrix> // eslint-disable-line no-use-before-define
|
|
19
19
|
|
|
20
|
-
private _tmpMatrix: Transform.Matrix
|
|
20
|
+
private _tmpMatrix: Transform.Matrix // eslint-disable-line no-use-before-define
|
|
21
21
|
private _tmpCanvas: HTMLCanvasElement
|
|
22
22
|
private _tmpCtx: CanvasRenderingContext2D
|
|
23
23
|
|
package/src/effect/visual.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
import { Movie } from '../movie'
|
|
2
|
+
import { Visual as VisualLayer } from '../layer/index'
|
|
1
3
|
import { Base } from './base'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Modifies the visual contents of a layer.
|
|
5
7
|
*/
|
|
6
|
-
export class Visual extends Base {
|
|
8
|
+
export class Visual extends Base {
|
|
9
|
+
// subclasses must implement apply
|
|
10
|
+
/**
|
|
11
|
+
* Apply this effect to a target at the given time
|
|
12
|
+
*
|
|
13
|
+
* @param target
|
|
14
|
+
* @param reltime - the movie's current time relative to the layer
|
|
15
|
+
* (will soon be replaced with an instance getter)
|
|
16
|
+
* @abstract
|
|
17
|
+
*/
|
|
18
|
+
apply (target: Movie | VisualLayer, reltime: number): void { // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
19
|
+
super.apply(target, reltime)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -134,7 +134,10 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
detach () {
|
|
137
|
-
|
|
137
|
+
// Cache dest before super.detach() unsets this.movie
|
|
138
|
+
const dest = this.movie.actx.destination
|
|
139
|
+
super.detach()
|
|
140
|
+
this.audioNode.disconnect(dest)
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
start () {
|
package/src/layer/base.ts
CHANGED
|
@@ -141,7 +141,8 @@ class Base implements EtroObject {
|
|
|
141
141
|
* The current time of the movie relative to this layer
|
|
142
142
|
*/
|
|
143
143
|
get currentTime (): number {
|
|
144
|
-
return this._movie
|
|
144
|
+
return this._movie
|
|
145
|
+
? this._movie.currentTime - this.startTime
|
|
145
146
|
: undefined
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -169,7 +170,7 @@ class Base implements EtroObject {
|
|
|
169
170
|
// id for events (independent of instance, but easy to access when on prototype
|
|
170
171
|
// chain)
|
|
171
172
|
Base.prototype.type = 'layer'
|
|
172
|
-
Base.prototype.publicExcludes = []
|
|
173
|
+
Base.prototype.publicExcludes = ['active']
|
|
173
174
|
Base.prototype.propertyFilters = {}
|
|
174
175
|
|
|
175
176
|
export { Base, BaseOptions }
|
package/src/layer/image.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Visual
|
|
2
|
-
import { VisualSourceMixin } from './visual-source'
|
|
1
|
+
import { Visual } from './visual'
|
|
2
|
+
import { VisualSourceMixin, VisualSourceOptions } from './visual-source'
|
|
3
3
|
|
|
4
|
-
type ImageOptions =
|
|
4
|
+
type ImageOptions = VisualSourceOptions
|
|
5
5
|
|
|
6
6
|
class Image extends VisualSourceMixin(Visual) {}
|
|
7
7
|
|
package/src/layer/text.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Dynamic, val, applyOptions } from '../util'
|
|
1
|
+
import { Dynamic, val, applyOptions, Color, parseColor } from '../util'
|
|
2
2
|
import { Visual, VisualOptions } from './visual'
|
|
3
3
|
|
|
4
4
|
interface TextOptions extends VisualOptions {
|
|
5
5
|
text: Dynamic<string>
|
|
6
6
|
font?: Dynamic<string>
|
|
7
|
-
color?: Dynamic<
|
|
7
|
+
color?: Dynamic<Color>
|
|
8
8
|
/** The text's horizontal offset from the layer */
|
|
9
9
|
textX?: Dynamic<number>
|
|
10
10
|
/** The text's vertical offset from the layer */
|
|
@@ -29,7 +29,7 @@ interface TextOptions extends VisualOptions {
|
|
|
29
29
|
class Text extends Visual {
|
|
30
30
|
text: Dynamic<string>
|
|
31
31
|
font: Dynamic<string>
|
|
32
|
-
color: Dynamic<
|
|
32
|
+
color: Dynamic<Color>
|
|
33
33
|
/** The text's horizontal offset from the layer */
|
|
34
34
|
textX: Dynamic<number>
|
|
35
35
|
/** The text's vertical offset from the layer */
|
|
@@ -124,7 +124,7 @@ class Text extends Visual {
|
|
|
124
124
|
background: null,
|
|
125
125
|
text: undefined, // required
|
|
126
126
|
font: '10px sans-serif',
|
|
127
|
-
color: '#fff',
|
|
127
|
+
color: parseColor('#fff'),
|
|
128
128
|
textX: 0,
|
|
129
129
|
textY: 0,
|
|
130
130
|
maxWidth: null,
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { Dynamic, val, applyOptions } from '../util'
|
|
2
|
-
import { Base, BaseOptions } from './base'
|
|
3
2
|
import { Visual, VisualOptions } from './visual'
|
|
4
3
|
|
|
5
4
|
type Constructor<T> = new (...args: unknown[]) => T
|
|
6
5
|
|
|
7
|
-
interface VisualSource extends
|
|
6
|
+
interface VisualSource extends Visual {
|
|
8
7
|
readonly source: HTMLImageElement | HTMLVideoElement
|
|
8
|
+
|
|
9
|
+
/** What part of {@link source} to render */
|
|
10
|
+
sourceX: Dynamic<number>
|
|
11
|
+
/** What part of {@link source} to render */
|
|
12
|
+
sourceY: Dynamic<number>
|
|
13
|
+
/** What part of {@link source} to render, or undefined for the entire width */
|
|
14
|
+
sourceWidth: Dynamic<number>
|
|
15
|
+
/** What part of {@link source} to render, or undefined for the entire height */
|
|
16
|
+
sourceHeight: Dynamic<number>
|
|
17
|
+
/** Where to render {@link source} onto the layer */
|
|
18
|
+
destX: Dynamic<number>
|
|
19
|
+
/** Where to render {@link source} onto the layer */
|
|
20
|
+
destY: Dynamic<number>
|
|
21
|
+
/** Where to render {@link source} onto the layer, or undefined to fill the layer's width */
|
|
22
|
+
destWidth: Dynamic<number>
|
|
23
|
+
/** Where to render {@link source} onto the layer, or undefined to fill the layer's height */
|
|
24
|
+
destHeight: Dynamic<number>
|
|
9
25
|
}
|
|
10
26
|
|
|
11
27
|
interface VisualSourceOptions extends VisualOptions {
|
|
@@ -32,7 +48,7 @@ interface VisualSourceOptions extends VisualOptions {
|
|
|
32
48
|
* A layer that gets its image data from an HTML image or video element
|
|
33
49
|
* @mixin VisualSourceMixin
|
|
34
50
|
*/
|
|
35
|
-
function VisualSourceMixin<OptionsSuperclass extends
|
|
51
|
+
function VisualSourceMixin<OptionsSuperclass extends VisualOptions> (superclass: Constructor<Visual>): Constructor<VisualSource> {
|
|
36
52
|
type MixedVisualSourceOptions = OptionsSuperclass & VisualSourceOptions
|
|
37
53
|
|
|
38
54
|
class MixedVisualSource extends superclass {
|
|
@@ -125,22 +141,26 @@ function VisualSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: C
|
|
|
125
141
|
// instead. (TODO: fact check)
|
|
126
142
|
/* eslint-disable eqeqeq */
|
|
127
143
|
return destWidth != undefined
|
|
128
|
-
? destWidth
|
|
144
|
+
? destWidth
|
|
145
|
+
: val(this, 'sourceWidth', this.currentTime)
|
|
129
146
|
},
|
|
130
147
|
destHeight: function (destHeight) {
|
|
131
148
|
/* eslint-disable eqeqeq */
|
|
132
149
|
return destHeight != undefined
|
|
133
|
-
? destHeight
|
|
150
|
+
? destHeight
|
|
151
|
+
: val(this, 'sourceHeight', this.currentTime)
|
|
134
152
|
},
|
|
135
153
|
width: function (width) {
|
|
136
154
|
/* eslint-disable eqeqeq */
|
|
137
155
|
return width != undefined
|
|
138
|
-
? width
|
|
156
|
+
? width
|
|
157
|
+
: val(this, 'destWidth', this.currentTime)
|
|
139
158
|
},
|
|
140
159
|
height: function (height) {
|
|
141
160
|
/* eslint-disable eqeqeq */
|
|
142
161
|
return height != undefined
|
|
143
|
-
? height
|
|
162
|
+
? height
|
|
163
|
+
: val(this, 'destHeight', this.currentTime)
|
|
144
164
|
}
|
|
145
165
|
}
|
|
146
166
|
|
package/src/layer/visual.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dynamic, val, applyOptions } from '../util'
|
|
1
|
+
import { Dynamic, val, applyOptions, Color } from '../util'
|
|
2
2
|
import { Base, BaseOptions } from './base'
|
|
3
3
|
import { Visual as VisualEffect } from '../effect/visual'
|
|
4
4
|
|
|
@@ -7,9 +7,9 @@ interface VisualOptions extends BaseOptions {
|
|
|
7
7
|
y?: Dynamic<number>
|
|
8
8
|
width?: Dynamic<number>
|
|
9
9
|
height?: Dynamic<number>
|
|
10
|
-
background?: Dynamic<
|
|
10
|
+
background?: Dynamic<Color>
|
|
11
11
|
border?: Dynamic<{
|
|
12
|
-
color:
|
|
12
|
+
color: Color
|
|
13
13
|
thickness?: number
|
|
14
14
|
}>
|
|
15
15
|
|
|
@@ -22,9 +22,9 @@ class Visual extends Base {
|
|
|
22
22
|
y: Dynamic<number>
|
|
23
23
|
width: Dynamic<number>
|
|
24
24
|
height: Dynamic<number>
|
|
25
|
-
background: Dynamic<
|
|
25
|
+
background: Dynamic<Color>
|
|
26
26
|
border: Dynamic<{
|
|
27
|
-
color:
|
|
27
|
+
color: Color
|
|
28
28
|
thickness: number
|
|
29
29
|
}>
|
|
30
30
|
|
|
@@ -169,13 +169,13 @@ class Visual extends Base {
|
|
|
169
169
|
height: null,
|
|
170
170
|
/**
|
|
171
171
|
* @name module:layer.Visual#background
|
|
172
|
-
* @desc The
|
|
172
|
+
* @desc The color code for the background, or <code>null</code> for
|
|
173
173
|
* transparency
|
|
174
174
|
*/
|
|
175
175
|
background: null,
|
|
176
176
|
/**
|
|
177
177
|
* @name module:layer.Visual#border
|
|
178
|
-
* @desc The
|
|
178
|
+
* @desc The border style, or <code>null</code> for no border
|
|
179
179
|
*/
|
|
180
180
|
border: null,
|
|
181
181
|
/**
|
package/src/movie.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { subscribe, publish } from './event'
|
|
6
|
-
import { Dynamic, val, clearCachedValues, applyOptions, watchPublic } from './util'
|
|
6
|
+
import { Dynamic, val, clearCachedValues, applyOptions, watchPublic, Color, parseColor } from './util'
|
|
7
7
|
import { Base as BaseLayer, Audio as AudioLayer, Video as VideoLayer, Visual } from './layer/index' // `Media` mixins
|
|
8
8
|
import { AudioSource } from './layer/audio-source' // not exported from ./layer/index
|
|
9
9
|
import { Base as BaseEffect } from './effect/index'
|
|
@@ -26,7 +26,7 @@ export class MovieOptions {
|
|
|
26
26
|
/** @deprecated Use <code>actx</code> instead */
|
|
27
27
|
audioContext?: AudioContext
|
|
28
28
|
/** The background color of the movie as a cSS string */
|
|
29
|
-
background?: Dynamic<
|
|
29
|
+
background?: Dynamic<Color>
|
|
30
30
|
repeat?: boolean
|
|
31
31
|
/** Call `refresh` when the user changes a property on the movie or any of its layers or effects */
|
|
32
32
|
autoRefresh?: boolean
|
|
@@ -37,21 +37,28 @@ export class MovieOptions {
|
|
|
37
37
|
*
|
|
38
38
|
* Implements a pub/sub system.
|
|
39
39
|
*/
|
|
40
|
-
// TODO: Implement event "durationchange", and more
|
|
41
|
-
// TODO: Add width and height options
|
|
42
40
|
// TODO: Make record option to make recording video output to the user while
|
|
43
41
|
// it's recording
|
|
44
42
|
// TODO: rename renderingFrame -> refreshing
|
|
45
43
|
export class Movie {
|
|
46
44
|
type: string
|
|
45
|
+
/**
|
|
46
|
+
* @deprecated Auto-refresh will be removed in the future (see issue #130).
|
|
47
|
+
*/
|
|
47
48
|
publicExcludes: string[]
|
|
48
49
|
propertyFilters: Record<string, <T>(value: T) => T>
|
|
49
50
|
|
|
50
51
|
repeat: boolean
|
|
51
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Call `refresh` when the user changes a property on the movie or any of its
|
|
54
|
+
* layers or effects
|
|
55
|
+
*
|
|
56
|
+
* @deprecated Auto-refresh will be removed in the future. If you want to
|
|
57
|
+
* refresh the canvas, call `refresh`. See issue #130.
|
|
58
|
+
*/
|
|
52
59
|
autoRefresh: boolean
|
|
53
60
|
/** The background color of the movie as a cSS string */
|
|
54
|
-
background: Dynamic<
|
|
61
|
+
background: Dynamic<Color>
|
|
55
62
|
/** The audio context to which audio output is sent during playback */
|
|
56
63
|
readonly actx: AudioContext
|
|
57
64
|
// Readonly because it's a proxy (so it can't be overwritten).
|
|
@@ -59,7 +66,7 @@ export class Movie {
|
|
|
59
66
|
// Readonly because it's a proxy (so it can't be overwritten).
|
|
60
67
|
readonly layers: BaseLayer[]
|
|
61
68
|
|
|
62
|
-
private _canvas: HTMLCanvasElement
|
|
69
|
+
private _canvas: HTMLCanvasElement
|
|
63
70
|
private _cctx: CanvasRenderingContext2D
|
|
64
71
|
private _effectsBack: BaseEffect[]
|
|
65
72
|
private _layersBack: BaseLayer[]
|
|
@@ -378,20 +385,34 @@ export class Movie {
|
|
|
378
385
|
return
|
|
379
386
|
}
|
|
380
387
|
|
|
381
|
-
this.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
388
|
+
const end = this.recording ? this._recordEndTime : this.duration
|
|
389
|
+
|
|
390
|
+
this._updateCurrentTime(timestamp, end)
|
|
391
|
+
|
|
392
|
+
// TODO: Is calling duration every frame bad for performance? (remember,
|
|
393
|
+
// it's calling Array.reduce)
|
|
394
|
+
if (this.currentTime === end) {
|
|
395
|
+
if (this.recording)
|
|
396
|
+
publish(this, 'movie.recordended', { movie: this })
|
|
397
|
+
|
|
398
|
+
if (this.currentTime === this.duration)
|
|
399
|
+
publish(this, 'movie.ended', { movie: this, repeat: this.repeat })
|
|
400
|
+
|
|
401
|
+
// TODO: only reset currentTime if repeating
|
|
402
|
+
if (this.repeat) {
|
|
403
|
+
// Don't use setter, which publishes 'movie.seek'. Instead, update the
|
|
404
|
+
// value and publish a 'movie.timeupdate' event.
|
|
405
|
+
this._currentTime = 0
|
|
406
|
+
publish(this, 'movie.timeupdate', { movie: this })
|
|
407
|
+
}
|
|
386
408
|
|
|
387
|
-
// Bad for performance? (remember, it's calling Array.reduce)
|
|
388
|
-
const end = this.duration
|
|
389
|
-
const ended = this.currentTime > end
|
|
390
|
-
if (ended) {
|
|
391
409
|
this._lastPlayed = performance.now()
|
|
392
410
|
this._lastPlayedOffset = 0 // this.currentTime
|
|
393
411
|
this._renderingFrame = false
|
|
394
|
-
|
|
412
|
+
|
|
413
|
+
// Stop playback or recording if done (except if it's playing and repeat
|
|
414
|
+
// is true)
|
|
415
|
+
if (!(!this.recording && this.repeat)) {
|
|
395
416
|
this._paused = true
|
|
396
417
|
this._ended = true
|
|
397
418
|
// Deactivate all layers
|
|
@@ -400,27 +421,18 @@ export class Movie {
|
|
|
400
421
|
const layer = this.layers[i]
|
|
401
422
|
// A layer that has been deleted before layers.length has been updated
|
|
402
423
|
// (see the layers proxy in the constructor).
|
|
403
|
-
if (!layer)
|
|
424
|
+
if (!layer || !layer.active)
|
|
404
425
|
continue
|
|
405
426
|
|
|
406
427
|
layer.stop()
|
|
407
428
|
layer.active = false
|
|
408
429
|
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
publish(this, 'movie.ended', { movie: this, repeat: this.repeat })
|
|
412
|
-
|
|
413
|
-
// TODO: only reset currentTime if repeating
|
|
414
|
-
this._currentTime = 0 // don't use setter
|
|
415
|
-
publish(this, 'movie.timeupdate', { movie: this })
|
|
416
|
-
}
|
|
417
430
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (done)
|
|
421
|
-
done()
|
|
431
|
+
if (done)
|
|
432
|
+
done()
|
|
422
433
|
|
|
423
|
-
|
|
434
|
+
return
|
|
435
|
+
}
|
|
424
436
|
}
|
|
425
437
|
|
|
426
438
|
// Do render
|
|
@@ -448,16 +460,22 @@ export class Movie {
|
|
|
448
460
|
}) // TODO: research performance cost
|
|
449
461
|
}
|
|
450
462
|
|
|
451
|
-
private _updateCurrentTime (
|
|
463
|
+
private _updateCurrentTime (timestampMs: number, end: number) {
|
|
452
464
|
// If we're only instant-rendering (current frame only), it doens't matter
|
|
453
465
|
// if it's paused or not.
|
|
454
466
|
if (!this._renderingFrame) {
|
|
455
467
|
// if ((timestamp - this._lastUpdate) >= this._updateInterval) {
|
|
456
|
-
const sinceLastPlayed = (
|
|
457
|
-
|
|
458
|
-
|
|
468
|
+
const sinceLastPlayed = (timestampMs - this._lastPlayed) / 1000
|
|
469
|
+
const currentTime = this._lastPlayedOffset + sinceLastPlayed // don't use setter
|
|
470
|
+
if (this.currentTime !== currentTime) {
|
|
471
|
+
this._currentTime = currentTime
|
|
472
|
+
publish(this, 'movie.timeupdate', { movie: this })
|
|
473
|
+
}
|
|
459
474
|
// this._lastUpdate = timestamp;
|
|
460
475
|
// }
|
|
476
|
+
|
|
477
|
+
if (this.currentTime > end)
|
|
478
|
+
this.currentTime = end
|
|
461
479
|
}
|
|
462
480
|
}
|
|
463
481
|
|
|
@@ -703,9 +721,9 @@ export class Movie {
|
|
|
703
721
|
canvas: undefined, // required
|
|
704
722
|
/**
|
|
705
723
|
* @name module:movie#background
|
|
706
|
-
* @desc The
|
|
724
|
+
* @desc The color for the background, or <code>null</code> for transparency
|
|
707
725
|
*/
|
|
708
|
-
background: '#000',
|
|
726
|
+
background: parseColor('#000'),
|
|
709
727
|
/**
|
|
710
728
|
* @name module:movie#repeat
|
|
711
729
|
*/
|
package/src/object.ts
CHANGED
|
@@ -4,7 +4,11 @@ import { Movie } from './movie'
|
|
|
4
4
|
export default interface EtroObject {
|
|
5
5
|
/** Used in etro internals */
|
|
6
6
|
type: string
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Which properties to not watch for changes, for `Movie#autoRefresh`
|
|
9
|
+
*
|
|
10
|
+
* @deprecated `Movie#autoRefresh` is deprecated
|
|
11
|
+
*/
|
|
8
12
|
publicExcludes: string[]
|
|
9
13
|
/** Map of property name to function to run on result of `val` */
|
|
10
14
|
propertyFilters: Record<string, <T>(value: T) => T>
|
package/src/util.ts
CHANGED
|
@@ -415,6 +415,8 @@ export function mapPixels (
|
|
|
415
415
|
* <p>Must be called before any watchable properties are set, and only once in
|
|
416
416
|
* the prototype chain.
|
|
417
417
|
*
|
|
418
|
+
* @deprecated Will be removed in the future (see issue #130)
|
|
419
|
+
*
|
|
418
420
|
* @param target - object to watch
|
|
419
421
|
*/
|
|
420
422
|
export function watchPublic (target: EtroObject): EtroObject {
|