etro 0.6.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/.github/workflows/nodejs.yml +4 -2
  2. package/CHANGELOG.md +85 -4
  3. package/CODE_OF_CONDUCT.md +5 -5
  4. package/CONTRIBUTING.md +33 -79
  5. package/README.md +93 -26
  6. package/dist/effect/base.d.ts +51 -0
  7. package/dist/effect/brightness.d.ts +16 -0
  8. package/dist/effect/channels.d.ts +23 -0
  9. package/dist/effect/chroma-key.d.ts +23 -0
  10. package/dist/effect/contrast.d.ts +15 -0
  11. package/dist/effect/elliptical-mask.d.ts +31 -0
  12. package/dist/effect/gaussian-blur.d.ts +60 -0
  13. package/dist/effect/grayscale.d.ts +7 -0
  14. package/dist/effect/index.d.ts +15 -0
  15. package/dist/effect/pixelate.d.ts +18 -0
  16. package/dist/effect/shader.d.ts +99 -0
  17. package/dist/effect/stack.d.ts +23 -0
  18. package/dist/effect/transform.d.ts +73 -0
  19. package/dist/etro-cjs.js +9387 -0
  20. package/dist/etro-iife.js +9390 -0
  21. package/dist/etro.d.ts +7 -0
  22. package/dist/event.d.ts +35 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/layer/audio-source.d.ts +24 -0
  25. package/dist/layer/audio.d.ts +14 -0
  26. package/dist/layer/base.d.ts +82 -0
  27. package/dist/layer/image.d.ts +6 -0
  28. package/dist/layer/index.d.ts +11 -0
  29. package/dist/layer/text.d.ts +60 -0
  30. package/dist/layer/video.d.ts +11 -0
  31. package/dist/layer/visual-source.d.ts +32 -0
  32. package/dist/layer/visual.d.ts +58 -0
  33. package/dist/movie.d.ts +192 -0
  34. package/dist/object.d.ts +12 -0
  35. package/dist/util.d.ts +125 -0
  36. package/eslint.conf.js +2 -9
  37. package/eslint.example-conf.js +9 -0
  38. package/eslint.test-conf.js +1 -0
  39. package/eslint.typescript-conf.js +5 -0
  40. package/examples/application/readme-screenshot.html +16 -17
  41. package/examples/application/video-player.html +10 -11
  42. package/examples/application/webcam.html +6 -6
  43. package/examples/introduction/audio.html +30 -18
  44. package/examples/introduction/effects.html +37 -14
  45. package/examples/introduction/export.html +40 -27
  46. package/examples/introduction/functions.html +6 -4
  47. package/examples/introduction/hello-world1.html +9 -5
  48. package/examples/introduction/hello-world2.html +5 -5
  49. package/examples/introduction/keyframes.html +35 -23
  50. package/examples/introduction/media.html +26 -18
  51. package/examples/introduction/text.html +9 -5
  52. package/karma.conf.js +6 -4
  53. package/package.json +34 -13
  54. package/rollup.config.js +19 -3
  55. package/scripts/gen-effect-samples.html +27 -26
  56. package/scripts/save-effect-samples.js +14 -15
  57. package/src/effect/base.ts +115 -0
  58. package/src/effect/brightness.ts +43 -0
  59. package/src/effect/channels.ts +50 -0
  60. package/src/effect/chroma-key.ts +82 -0
  61. package/src/effect/contrast.ts +42 -0
  62. package/src/effect/elliptical-mask.ts +75 -0
  63. package/src/effect/gaussian-blur.ts +232 -0
  64. package/src/effect/grayscale.ts +34 -0
  65. package/src/effect/index.ts +22 -0
  66. package/src/effect/pixelate.ts +58 -0
  67. package/src/effect/shader.ts +557 -0
  68. package/src/effect/stack.ts +77 -0
  69. package/src/effect/transform.ts +193 -0
  70. package/src/etro.ts +26 -0
  71. package/src/event.ts +112 -0
  72. package/src/index.ts +8 -0
  73. package/src/layer/audio-source.ts +219 -0
  74. package/src/layer/audio.ts +34 -0
  75. package/src/layer/base.ts +175 -0
  76. package/src/layer/image.ts +8 -0
  77. package/src/layer/index.ts +13 -0
  78. package/src/layer/text.ts +138 -0
  79. package/src/layer/video.ts +15 -0
  80. package/src/layer/visual-source.ts +150 -0
  81. package/src/layer/visual.ts +197 -0
  82. package/src/movie.ts +701 -0
  83. package/src/object.ts +14 -0
  84. package/src/util.ts +466 -0
  85. package/tsconfig.json +8 -0
  86. package/dist/etro.js +0 -3397
  87. package/docs/effect.js.html +0 -1215
  88. package/docs/event.js.html +0 -145
  89. package/docs/index.html +0 -81
  90. package/docs/index.js.html +0 -92
  91. package/docs/layer.js.html +0 -888
  92. package/docs/module-effect-GaussianBlurComponent.html +0 -345
  93. package/docs/module-effect.Brightness.html +0 -339
  94. package/docs/module-effect.Channels.html +0 -319
  95. package/docs/module-effect.ChromaKey.html +0 -611
  96. package/docs/module-effect.Contrast.html +0 -339
  97. package/docs/module-effect.EllipticalMask.html +0 -200
  98. package/docs/module-effect.GaussianBlur.html +0 -202
  99. package/docs/module-effect.GaussianBlurHorizontal.html +0 -242
  100. package/docs/module-effect.GaussianBlurVertical.html +0 -242
  101. package/docs/module-effect.Pixelate.html +0 -330
  102. package/docs/module-effect.Shader.html +0 -1227
  103. package/docs/module-effect.Stack.html +0 -406
  104. package/docs/module-effect.Transform.Matrix.html +0 -193
  105. package/docs/module-effect.Transform.html +0 -1174
  106. package/docs/module-effect.html +0 -148
  107. package/docs/module-event.html +0 -473
  108. package/docs/module-index.html +0 -186
  109. package/docs/module-layer-Media.html +0 -1116
  110. package/docs/module-layer-MediaMixin.html +0 -164
  111. package/docs/module-layer.Audio.html +0 -1188
  112. package/docs/module-layer.Base.html +0 -629
  113. package/docs/module-layer.Image.html +0 -1421
  114. package/docs/module-layer.Text.html +0 -1731
  115. package/docs/module-layer.Video.html +0 -1938
  116. package/docs/module-layer.Visual.html +0 -1698
  117. package/docs/module-layer.html +0 -137
  118. package/docs/module-movie.html +0 -3118
  119. package/docs/module-util.Color.html +0 -702
  120. package/docs/module-util.Font.html +0 -395
  121. package/docs/module-util.html +0 -845
  122. package/docs/movie.js.html +0 -689
  123. package/docs/scripts/collapse.js +0 -20
  124. package/docs/scripts/linenumber.js +0 -25
  125. package/docs/scripts/nav.js +0 -12
  126. package/docs/scripts/polyfill.js +0 -4
  127. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  128. package/docs/scripts/prettify/lang-css.js +0 -2
  129. package/docs/scripts/prettify/prettify.js +0 -28
  130. package/docs/scripts/search.js +0 -83
  131. package/docs/styles/jsdoc.css +0 -671
  132. package/docs/styles/prettify.css +0 -79
  133. package/docs/util.js.html +0 -503
  134. package/screenshots/2019-08-17_0.png +0 -0
  135. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  136. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  137. package/spec/assets/effect/original.png +0 -0
  138. package/spec/assets/effect/pixelate.png +0 -0
  139. package/spec/assets/effect/transform/multiply.png +0 -0
  140. package/spec/assets/effect/transform/rotate.png +0 -0
  141. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  142. package/spec/assets/effect/transform/scale.png +0 -0
  143. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  144. package/spec/assets/effect/transform/translate.png +0 -0
  145. package/spec/assets/layer/audio.wav +0 -0
  146. package/spec/assets/layer/image.jpg +0 -0
  147. package/spec/effect.spec.js +0 -352
  148. package/spec/event.spec.js +0 -25
  149. package/spec/layer.spec.js +0 -128
  150. package/spec/movie.spec.js +0 -154
  151. package/spec/util.spec.js +0 -285
  152. package/src/effect.js +0 -1265
  153. package/src/event.js +0 -78
  154. package/src/index.js +0 -23
  155. package/src/layer.js +0 -875
  156. package/src/movie.js +0 -636
  157. package/src/util.js +0 -487
@@ -0,0 +1,193 @@
1
+ import { Visual } from '../layer/index'
2
+ import { Movie } from '../movie'
3
+ import { val, Dynamic } from '../util'
4
+ import { Base } from './base'
5
+
6
+ export interface TransformOptions {
7
+ matrix: Dynamic<Transform.Matrix> // eslint-disable-line no-use-before-define
8
+ }
9
+
10
+ /**
11
+ * Transforms a layer or movie using a transformation matrix. Use {@link
12
+ * Transform.Matrix} to either A) calculate those values based on a series of
13
+ * translations, scalings and rotations) or B) input the matrix values
14
+ * directly, using the optional argument in the constructor.
15
+ */
16
+ class Transform extends Base {
17
+ /** Matrix that determines how to transform the target */
18
+ matrix: Dynamic<Transform.Matrix>
19
+
20
+ private _tmpMatrix: Transform.Matrix
21
+ private _tmpCanvas: HTMLCanvasElement
22
+ private _tmpCtx: CanvasRenderingContext2D
23
+
24
+ /**
25
+ * @param matrix - matrix that determines how to transform the target
26
+ */
27
+ constructor (options: TransformOptions) {
28
+ super()
29
+ /**
30
+ * How to transform the target
31
+ */
32
+ this.matrix = options.matrix
33
+ this._tmpMatrix = new Transform.Matrix()
34
+ this._tmpCanvas = document.createElement('canvas')
35
+ this._tmpCtx = this._tmpCanvas.getContext('2d')
36
+ }
37
+
38
+ apply (target: Movie | Visual, reltime: number): void {
39
+ if (target.canvas.width !== this._tmpCanvas.width)
40
+ this._tmpCanvas.width = target.canvas.width
41
+
42
+ if (target.canvas.height !== this._tmpCanvas.height)
43
+ this._tmpCanvas.height = target.canvas.height
44
+
45
+ // Use data, since that's the underlying storage
46
+ this._tmpMatrix.data = val(this, 'matrix.data', reltime)
47
+
48
+ this._tmpCtx.setTransform(
49
+ this._tmpMatrix.a, this._tmpMatrix.b, this._tmpMatrix.c,
50
+ this._tmpMatrix.d, this._tmpMatrix.e, this._tmpMatrix.f
51
+ )
52
+ this._tmpCtx.drawImage(target.canvas, 0, 0)
53
+ // Assume it was identity for now
54
+ this._tmpCtx.setTransform(1, 0, 0, 0, 1, 0)
55
+ target.cctx.clearRect(0, 0, target.canvas.width, target.canvas.height)
56
+ target.cctx.drawImage(this._tmpCanvas, 0, 0)
57
+ }
58
+ }
59
+
60
+ namespace Transform { // eslint-disable-line @typescript-eslint/no-namespace
61
+
62
+ /**
63
+ * @class
64
+ * A 3x3 matrix for storing 2d transformations
65
+ */
66
+ export class Matrix {
67
+ /**
68
+ * The identity matrix
69
+ */
70
+ static IDENTITY = new Matrix()
71
+ private static _TMP_MATRIX = new Matrix()
72
+
73
+ data: number[]
74
+
75
+ constructor (data?: number[]) {
76
+ this.data = data || [
77
+ 1, 0, 0,
78
+ 0, 1, 0,
79
+ 0, 0, 1
80
+ ]
81
+ }
82
+
83
+ identity (): Matrix {
84
+ for (let i = 0; i < this.data.length; i++)
85
+ this.data[i] = Matrix.IDENTITY.data[i]
86
+
87
+ return this
88
+ }
89
+
90
+ /**
91
+ * @param x
92
+ * @param y
93
+ * @param [val]
94
+ */
95
+ cell (x: number, y: number, val?: number): number {
96
+ if (val !== undefined)
97
+ this.data[3 * y + x] = val
98
+
99
+ return this.data[3 * y + x]
100
+ }
101
+
102
+ /* For canvas context setTransform */
103
+ get a (): number {
104
+ return this.data[0]
105
+ }
106
+
107
+ get b (): number {
108
+ return this.data[3]
109
+ }
110
+
111
+ get c (): number {
112
+ return this.data[1]
113
+ }
114
+
115
+ get d (): number {
116
+ return this.data[4]
117
+ }
118
+
119
+ get e (): number {
120
+ return this.data[2]
121
+ }
122
+
123
+ get f (): number {
124
+ return this.data[5]
125
+ }
126
+
127
+ /**
128
+ * Combines <code>this</code> with another matrix <code>other</code>
129
+ * @param other
130
+ */
131
+ multiply (other: Matrix): Matrix {
132
+ // copy to temporary matrix to avoid modifying `this` while reading from it
133
+ for (let x = 0; x < 3; x++)
134
+ for (let y = 0; y < 3; y++) {
135
+ let sum = 0
136
+ for (let i = 0; i < 3; i++)
137
+ sum += this.cell(x, i) * other.cell(i, y)
138
+
139
+ Matrix._TMP_MATRIX.cell(x, y, sum)
140
+ }
141
+
142
+ // copy data from TMP_MATRIX to this
143
+ for (let i = 0; i < Matrix._TMP_MATRIX.data.length; i++)
144
+ this.data[i] = Matrix._TMP_MATRIX.data[i]
145
+
146
+ return this
147
+ }
148
+
149
+ /**
150
+ * @param x
151
+ * @param y
152
+ */
153
+ translate (x: number, y: number): Matrix {
154
+ this.multiply(new Matrix([
155
+ 1, 0, x,
156
+ 0, 1, y,
157
+ 0, 0, 1
158
+ ]))
159
+
160
+ return this
161
+ }
162
+
163
+ /**
164
+ * @param x
165
+ * @param y
166
+ */
167
+ scale (x: number, y: number): Matrix {
168
+ this.multiply(new Matrix([
169
+ x, 0, 0,
170
+ 0, y, 0,
171
+ 0, 0, 1
172
+ ]))
173
+
174
+ return this
175
+ }
176
+
177
+ /**
178
+ * @param a - the angle or rotation in radians
179
+ */
180
+ rotate (a: number): Matrix {
181
+ const c = Math.cos(a); const s = Math.sin(a)
182
+ this.multiply(new Matrix([
183
+ c, s, 0,
184
+ -s, c, 0,
185
+ 0, 0, 1
186
+ ]))
187
+
188
+ return this
189
+ }
190
+ }
191
+ }
192
+
193
+ export { Transform }
package/src/etro.ts ADDED
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Typedoc can't handle default exports. To let users import default export and
3
+ * make typedoc work, this module exports everything as named exports. Then,
4
+ * ./index imports everything from this module and exports it as a default
5
+ * export. Typedoc uses this file, and rollup and NPM use ./index
6
+ */
7
+
8
+ // TODO: investigate possibility of changing movie (canvas) width/height after
9
+ // layers added. I think it's fine, but still make sure.
10
+ // TODO: create built-in audio gain node for volume control in movie and/or
11
+ // layer.
12
+ // TODO: figure out InvalidStateError in beginning only when reloaded
13
+
14
+ import * as layer from './layer/index'
15
+ import * as effect from './effect/index'
16
+ import * as event from './event'
17
+ import EtroObject from './object'
18
+
19
+ export * from './movie'
20
+ export * from './util'
21
+ export {
22
+ EtroObject,
23
+ layer,
24
+ effect,
25
+ event
26
+ }
package/src/event.ts ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @module event
3
+ */
4
+
5
+ import EtroObject from './object'
6
+
7
+ export interface Event {
8
+ target: EtroObject
9
+ type: string
10
+ }
11
+
12
+ /**
13
+ * An event type
14
+ * @private
15
+ */
16
+ class TypeId {
17
+ private _parts: string[]
18
+
19
+ constructor (id) {
20
+ this._parts = id.split('.')
21
+ }
22
+
23
+ contains (other) {
24
+ if (other._parts.length > this._parts.length)
25
+ return false
26
+
27
+ for (let i = 0; i < other._parts.length; i++)
28
+ if (other._parts[i] !== this._parts[i])
29
+ return false
30
+
31
+ return true
32
+ }
33
+
34
+ toString () {
35
+ return this._parts.join('.')
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Listen for an event or category of events
41
+ *
42
+ * @param target - a etro object
43
+ * @param type - the id of the type (can contain subtypes, such as
44
+ * "type.subtype")
45
+ * @param listener
46
+ */
47
+ export function subscribe (target: EtroObject, type: string, listener: <T extends Event>(T) => void): void {
48
+ if (!listeners.has(target))
49
+ listeners.set(target, [])
50
+
51
+ listeners.get(target).push(
52
+ { type: new TypeId(type), listener }
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Remove an event listener
58
+ *
59
+ * @param target - a etro object
60
+ * @param type - the id of the type (can contain subtypes, such as
61
+ * "type.subtype")
62
+ * @param listener
63
+ */
64
+ export function unsubscribe (target: EtroObject, listener: <T extends Event>(T) => void): void {
65
+ // Make sure `listener` has been added with `subscribe`.
66
+ if (!listeners.has(target) ||
67
+ !listeners.get(target).map(pair => pair.listener).includes(listener))
68
+ throw new Error('No matching event listener to remove')
69
+
70
+ const removed = listeners.get(target)
71
+ .filter(pair => pair.listener !== listener)
72
+ listeners.set(target, removed)
73
+ }
74
+
75
+ /**
76
+ * Emits an event to all listeners
77
+ *
78
+ * @param target - a etro object
79
+ * @param type - the id of the type (can contain subtypes, such as
80
+ * "type.subtype")
81
+ * @param event - any additional event data
82
+ */
83
+ export function publish (target: EtroObject, type: string, event: Record<string, unknown>): Event {
84
+ (event as unknown as Event).target = target; // could be a proxy
85
+ (event as unknown as Event).type = type
86
+
87
+ const t = new TypeId(type)
88
+
89
+ if (!listeners.has(target))
90
+ // No event fired
91
+ return null
92
+
93
+ // Call event listeners for this event.
94
+ const listenersForType = []
95
+ for (let i = 0; i < listeners.get(target).length; i++) {
96
+ const item = listeners.get(target)[i]
97
+ if (t.contains(item.type))
98
+ listenersForType.push(item.listener)
99
+ }
100
+
101
+ for (let i = 0; i < listenersForType.length; i++) {
102
+ const listener = listenersForType[i]
103
+ listener(event)
104
+ }
105
+
106
+ return event as unknown as Event
107
+ }
108
+
109
+ const listeners: WeakMap<EtroObject, {
110
+ type: TypeId,
111
+ listener: (Event) => void
112
+ }[]> = new WeakMap()
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The entry point
3
+ * @module index
4
+ */
5
+
6
+ import * as etro from './etro'
7
+
8
+ export default etro
@@ -0,0 +1,219 @@
1
+ import { AudioContext, IAudioNode, IAudioDestinationNode } from 'standardized-audio-context'
2
+ import { Movie } from '../movie'
3
+ import { subscribe } from '../event'
4
+ import { applyOptions, val } from '../util'
5
+ import { Base, BaseOptions } from './base'
6
+
7
+ type Constructor<T> = new (...args: unknown[]) => T
8
+
9
+ interface AudioSource extends Base {
10
+ readonly source: HTMLMediaElement
11
+ readonly audioNode: IAudioNode<AudioContext>
12
+ playbackRate: number
13
+ /** The audio source node for the media */
14
+ sourceStartTime: number
15
+ }
16
+
17
+ interface AudioSourceOptions extends BaseOptions {
18
+ source: HTMLMediaElement
19
+ sourceStartTime?: number
20
+ muted?: boolean
21
+ volume?: number
22
+ playbackRate: number
23
+ onload?: (source: HTMLMediaElement, options: AudioSourceOptions) => void
24
+ }
25
+
26
+ /**
27
+ * A layer that gets its audio from an HTMLMediaElement
28
+ * @mixin AudioSourceMixin
29
+ */
30
+ // TODO: Implement playback rate
31
+ // The generic is just for type-checking. The argument is for functionality
32
+ // (survives when compiled to javascript).
33
+
34
+ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Constructor<Base>): Constructor<AudioSource> {
35
+ type MixedAudioSourceOptions = OptionsSuperclass & AudioSourceOptions
36
+
37
+ class MixedAudioSource extends superclass {
38
+ /**
39
+ * The raw html media element
40
+ */
41
+ readonly source: HTMLMediaElement
42
+
43
+ private __startTime: number
44
+ private _audioNode: IAudioNode<AudioContext>
45
+ private _sourceStartTime: number
46
+ private _unstretchedDuration: number
47
+ private _playbackRate: number
48
+ private _initialized: boolean
49
+ private _connectedToDestination: boolean
50
+
51
+ /**
52
+ * @param options
53
+ * @param options.source
54
+ * @param options.onload
55
+ * @param [options.sourceStartTime=0] - at what time in the audio
56
+ * the layer starts
57
+ * @param [options.duration=media.duration-options.sourceStartTime]
58
+ * @param [options.muted=false]
59
+ * @param [options.volume=1]
60
+ * @param [options.playbackRate=1]
61
+ */
62
+ constructor (options: MixedAudioSourceOptions) {
63
+ const onload = options.onload
64
+ // Don't set as instance property
65
+ delete options.onload
66
+ super(options)
67
+ this._initialized = false
68
+ this._sourceStartTime = options.sourceStartTime || 0
69
+ applyOptions(options, this)
70
+
71
+ const load = () => {
72
+ // TODO: && ?
73
+ if ((options.duration || (this.source.duration - this.sourceStartTime)) < 0)
74
+ throw new Error('Invalid options.duration or options.sourceStartTime')
75
+
76
+ this._unstretchedDuration = options.duration || (this.source.duration - this.sourceStartTime)
77
+ this.duration = this._unstretchedDuration / (this.playbackRate)
78
+ // onload will use `this`, and can't bind itself because it's before
79
+ // super()
80
+ onload && onload.bind(this)(this.source, options)
81
+ }
82
+ if (this.source.readyState >= 2)
83
+ // this frame's data is available now
84
+ load()
85
+ else
86
+ // when this frame's data is available
87
+ this.source.addEventListener('loadedmetadata', load)
88
+
89
+ this.source.addEventListener('durationchange', () => {
90
+ this.duration = options.duration || (this.source.duration - this.sourceStartTime)
91
+ })
92
+ }
93
+
94
+ attach (movie: Movie) {
95
+ super.attach(movie)
96
+
97
+ subscribe(movie, 'movie.seek', () => {
98
+ if (this.currentTime < 0 || this.currentTime >= this.duration)
99
+ return
100
+
101
+ this.source.currentTime = this.currentTime + this.sourceStartTime
102
+ })
103
+
104
+ // TODO: on unattach?
105
+ subscribe(movie, 'movie.audiodestinationupdate', event => {
106
+ // Connect to new destination if immeidately connected to the existing
107
+ // destination.
108
+ if (this._connectedToDestination) {
109
+ this.audioNode.disconnect(movie.actx.destination)
110
+ this.audioNode.connect(event.destination)
111
+ }
112
+ })
113
+
114
+ // connect to audiocontext
115
+ this._audioNode = this.audioNode || movie.actx.createMediaElementSource(this.source)
116
+
117
+ // Spy on connect and disconnect to remember if it connected to
118
+ // actx.destination (for Movie#record).
119
+ const oldConnect = this._audioNode.connect.bind(this.audioNode)
120
+ this._audioNode.connect = <T extends IAudioDestinationNode<AudioContext>>(destination: T, outputIndex?: number, inputIndex?: number): AudioNode => {
121
+ this._connectedToDestination = destination === movie.actx.destination
122
+ return oldConnect(destination, outputIndex, inputIndex)
123
+ }
124
+ const oldDisconnect = this._audioNode.disconnect.bind(this.audioNode)
125
+ this._audioNode.disconnect = <T extends IAudioDestinationNode<AudioContext>>(destination?: T | number, output?: number, input?: number): AudioNode => {
126
+ if (this._connectedToDestination &&
127
+ destination === movie.actx.destination)
128
+ this._connectedToDestination = false
129
+
130
+ return oldDisconnect(destination, output, input)
131
+ }
132
+
133
+ // Connect to actx.destination by default (can be rewired by user)
134
+ this.audioNode.connect(movie.actx.destination)
135
+ }
136
+
137
+ detach () {
138
+ this.audioNode.disconnect(this.movie.actx.destination)
139
+ }
140
+
141
+ start () {
142
+ this.source.currentTime = this.currentTime + this.sourceStartTime
143
+ this.source.play()
144
+ }
145
+
146
+ render () {
147
+ super.render()
148
+ // TODO: implement Issue: Create built-in audio node to support built-in
149
+ // audio nodes, as this does nothing rn
150
+ this.source.muted = val(this, 'muted', this.currentTime)
151
+ this.source.volume = val(this, 'volume', this.currentTime)
152
+ this.source.playbackRate = val(this, 'playbackRate', this.currentTime)
153
+ }
154
+
155
+ stop () {
156
+ this.source.pause()
157
+ }
158
+
159
+ /**
160
+ * The audio source node for the media
161
+ */
162
+ get audioNode () {
163
+ return this._audioNode
164
+ }
165
+
166
+ get playbackRate () {
167
+ return this._playbackRate
168
+ }
169
+
170
+ set playbackRate (value) {
171
+ this._playbackRate = value
172
+ if (this._unstretchedDuration !== undefined)
173
+ this.duration = this._unstretchedDuration / value
174
+ }
175
+
176
+ get startTime () {
177
+ return this.__startTime
178
+ }
179
+
180
+ set startTime (val) {
181
+ this.__startTime = val
182
+ if (this._initialized) {
183
+ const mediaProgress = this.movie.currentTime - this.startTime
184
+ this.source.currentTime = this.sourceStartTime + mediaProgress
185
+ }
186
+ }
187
+
188
+ set sourceStartTime (val) {
189
+ this._sourceStartTime = val
190
+ if (this._initialized) {
191
+ const mediaProgress = this.movie.currentTime - this.startTime
192
+ this.source.currentTime = mediaProgress + this.sourceStartTime
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Time in the media at which the layer starts
198
+ */
199
+ get sourceStartTime () {
200
+ return this._sourceStartTime
201
+ }
202
+
203
+ getDefaultOptions (): MixedAudioSourceOptions {
204
+ return {
205
+ ...superclass.prototype.getDefaultOptions(),
206
+ source: undefined, // required
207
+ sourceStartTime: 0,
208
+ duration: undefined, // important to include undefined keys, for applyOptions
209
+ muted: false,
210
+ volume: 1,
211
+ playbackRate: 1
212
+ }
213
+ }
214
+ }
215
+
216
+ return MixedAudioSource
217
+ }
218
+
219
+ export { AudioSource, AudioSourceOptions, AudioSourceMixin }
@@ -0,0 +1,34 @@
1
+ // TODO: rename to something more consistent with the naming convention of Visual and VisualSourceMixin
2
+
3
+ import { Base, BaseOptions } from './base'
4
+ import { AudioSourceMixin, AudioSourceOptions } from './audio-source'
5
+
6
+ type AudioOptions = AudioSourceOptions
7
+
8
+ /**
9
+ * @extends AudioSource
10
+ */
11
+ class Audio extends AudioSourceMixin<BaseOptions>(Base) {
12
+ /**
13
+ * Creates an audio layer
14
+ */
15
+ constructor (options: AudioOptions) {
16
+ super(options)
17
+ if (this.duration === undefined)
18
+ this.duration = (this).source.duration - this.sourceStartTime
19
+ }
20
+
21
+ getDefaultOptions (): AudioOptions {
22
+ return {
23
+ ...Object.getPrototypeOf(this).getDefaultOptions(),
24
+ /**
25
+ * @name module:layer.Audio#sourceStartTime
26
+ * @desc Where in the media to start playing when the layer starts
27
+ */
28
+ sourceStartTime: 0,
29
+ duration: undefined
30
+ }
31
+ }
32
+ }
33
+
34
+ export { Audio, AudioOptions }