etro 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.github/workflows/nodejs.yml +1 -1
  2. package/CHANGELOG.md +45 -1
  3. package/CONTRIBUTING.md +23 -19
  4. package/README.md +81 -26
  5. package/dist/effect/base.d.ts +38 -0
  6. package/dist/effect/brightness.d.ts +16 -0
  7. package/dist/effect/channels.d.ts +23 -0
  8. package/dist/effect/chroma-key.d.ts +23 -0
  9. package/dist/effect/contrast.d.ts +15 -0
  10. package/dist/effect/elliptical-mask.d.ts +31 -0
  11. package/dist/effect/gaussian-blur.d.ts +60 -0
  12. package/dist/effect/grayscale.d.ts +7 -0
  13. package/dist/effect/index.d.ts +15 -0
  14. package/dist/effect/pixelate.d.ts +18 -0
  15. package/dist/effect/shader.d.ts +99 -0
  16. package/dist/effect/stack.d.ts +23 -0
  17. package/dist/effect/transform.d.ts +73 -0
  18. package/dist/etro-cjs.js +9337 -3331
  19. package/dist/etro-iife.js +9279 -3273
  20. package/dist/etro.d.ts +7 -0
  21. package/dist/event.d.ts +35 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/layer/audio-source.d.ts +24 -0
  24. package/dist/layer/audio.d.ts +14 -0
  25. package/dist/layer/base.d.ts +69 -0
  26. package/dist/layer/image.d.ts +6 -0
  27. package/dist/layer/index.d.ts +11 -0
  28. package/dist/layer/text.d.ts +60 -0
  29. package/dist/layer/video.d.ts +11 -0
  30. package/dist/layer/visual-source.d.ts +32 -0
  31. package/dist/layer/visual.d.ts +58 -0
  32. package/dist/movie.d.ts +192 -0
  33. package/dist/object.d.ts +12 -0
  34. package/dist/util.d.ts +125 -0
  35. package/eslint.conf.js +0 -8
  36. package/eslint.example-conf.js +9 -0
  37. package/eslint.typescript-conf.js +5 -0
  38. package/examples/application/readme-screenshot.html +12 -9
  39. package/examples/application/video-player.html +7 -7
  40. package/examples/application/webcam.html +6 -6
  41. package/examples/introduction/audio.html +30 -18
  42. package/examples/introduction/effects.html +14 -10
  43. package/examples/introduction/export.html +32 -25
  44. package/examples/introduction/functions.html +6 -4
  45. package/examples/introduction/hello-world1.html +9 -5
  46. package/examples/introduction/hello-world2.html +5 -5
  47. package/examples/introduction/keyframes.html +35 -23
  48. package/examples/introduction/media.html +26 -18
  49. package/examples/introduction/text.html +9 -5
  50. package/karma.conf.js +1 -1
  51. package/package.json +29 -13
  52. package/rollup.config.js +15 -4
  53. package/scripts/gen-effect-samples.html +29 -25
  54. package/scripts/save-effect-samples.js +14 -15
  55. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  56. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  57. package/spec/assets/effect/grayscale.png +0 -0
  58. package/spec/assets/effect/original.png +0 -0
  59. package/spec/assets/effect/pixelate.png +0 -0
  60. package/spec/assets/effect/transform/multiply.png +0 -0
  61. package/spec/assets/effect/transform/rotate.png +0 -0
  62. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  63. package/spec/assets/effect/transform/scale.png +0 -0
  64. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  65. package/spec/assets/effect/transform/translate.png +0 -0
  66. package/spec/effect.spec.js +126 -57
  67. package/spec/event.spec.js +14 -0
  68. package/spec/layer.spec.js +175 -18
  69. package/spec/movie.spec.js +191 -7
  70. package/spec/util.spec.js +14 -5
  71. package/src/effect/base.ts +96 -0
  72. package/src/effect/brightness.ts +43 -0
  73. package/src/effect/channels.ts +50 -0
  74. package/src/effect/chroma-key.ts +82 -0
  75. package/src/effect/contrast.ts +42 -0
  76. package/src/effect/elliptical-mask.ts +75 -0
  77. package/src/effect/gaussian-blur.ts +232 -0
  78. package/src/effect/grayscale.ts +34 -0
  79. package/src/effect/index.ts +22 -0
  80. package/src/effect/pixelate.ts +59 -0
  81. package/src/effect/shader.ts +561 -0
  82. package/src/effect/stack.ts +74 -0
  83. package/src/effect/transform.ts +194 -0
  84. package/src/etro.ts +26 -0
  85. package/src/event.ts +118 -0
  86. package/src/index.ts +8 -0
  87. package/src/layer/audio-source.ts +217 -0
  88. package/src/layer/audio.ts +35 -0
  89. package/src/layer/base.ts +156 -0
  90. package/src/layer/image.ts +8 -0
  91. package/src/layer/index.ts +13 -0
  92. package/src/layer/text.ts +138 -0
  93. package/src/layer/video.ts +15 -0
  94. package/src/layer/visual-source.ts +150 -0
  95. package/src/layer/visual.ts +198 -0
  96. package/src/movie.ts +709 -0
  97. package/src/object.ts +14 -0
  98. package/src/util.ts +473 -0
  99. package/tsconfig.json +8 -0
  100. package/screenshots/2019-08-17_0.png +0 -0
  101. package/src/effect.js +0 -1268
  102. package/src/event.js +0 -78
  103. package/src/index.js +0 -23
  104. package/src/layer.js +0 -897
  105. package/src/movie.js +0 -637
  106. package/src/util.js +0 -505
@@ -0,0 +1,194 @@
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
+
88
+ return this
89
+ }
90
+
91
+ /**
92
+ * @param x
93
+ * @param y
94
+ * @param [val]
95
+ */
96
+ cell (x: number, y: number, val?: number): number {
97
+ if (val !== undefined) {
98
+ this.data[3 * y + x] = val
99
+ }
100
+ return this.data[3 * y + x]
101
+ }
102
+
103
+ /* For canvas context setTransform */
104
+ get a (): number {
105
+ return this.data[0]
106
+ }
107
+
108
+ get b (): number {
109
+ return this.data[3]
110
+ }
111
+
112
+ get c (): number {
113
+ return this.data[1]
114
+ }
115
+
116
+ get d (): number {
117
+ return this.data[4]
118
+ }
119
+
120
+ get e (): number {
121
+ return this.data[2]
122
+ }
123
+
124
+ get f (): number {
125
+ return this.data[5]
126
+ }
127
+
128
+ /**
129
+ * Combines <code>this</code> with another matrix <code>other</code>
130
+ * @param other
131
+ */
132
+ multiply (other: Matrix): Matrix {
133
+ // copy to temporary matrix to avoid modifying `this` while reading from it
134
+ for (let x = 0; x < 3; x++) {
135
+ for (let y = 0; y < 3; y++) {
136
+ let sum = 0
137
+ for (let i = 0; i < 3; i++) {
138
+ sum += this.cell(x, i) * other.cell(i, y)
139
+ }
140
+ Matrix._TMP_MATRIX.cell(x, y, sum)
141
+ }
142
+ }
143
+ // copy data from TMP_MATRIX to this
144
+ for (let i = 0; i < Matrix._TMP_MATRIX.data.length; i++) {
145
+ this.data[i] = Matrix._TMP_MATRIX.data[i]
146
+ }
147
+ return this
148
+ }
149
+
150
+ /**
151
+ * @param x
152
+ * @param y
153
+ */
154
+ translate (x: number, y: number): Matrix {
155
+ this.multiply(new Matrix([
156
+ 1, 0, x,
157
+ 0, 1, y,
158
+ 0, 0, 1
159
+ ]))
160
+
161
+ return this
162
+ }
163
+
164
+ /**
165
+ * @param x
166
+ * @param y
167
+ */
168
+ scale (x: number, y: number): Matrix {
169
+ this.multiply(new Matrix([
170
+ x, 0, 0,
171
+ 0, y, 0,
172
+ 0, 0, 1
173
+ ]))
174
+
175
+ return this
176
+ }
177
+
178
+ /**
179
+ * @param a - the angle or rotation in radians
180
+ */
181
+ rotate (a: number): Matrix {
182
+ const c = Math.cos(a); const s = Math.sin(a)
183
+ this.multiply(new Matrix([
184
+ c, s, 0,
185
+ -s, c, 0,
186
+ 0, 0, 1
187
+ ]))
188
+
189
+ return this
190
+ }
191
+ }
192
+ }
193
+
194
+ 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,118 @@
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
+
28
+ for (let i = 0; i < other._parts.length; i++) {
29
+ if (other._parts[i] !== this._parts[i]) {
30
+ return false
31
+ }
32
+ }
33
+ return true
34
+ }
35
+
36
+ toString () {
37
+ return this._parts.join('.')
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Listen for an event or category of events
43
+ *
44
+ * @param target - a etro object
45
+ * @param type - the id of the type (can contain subtypes, such as
46
+ * "type.subtype")
47
+ * @param listener
48
+ */
49
+ export function subscribe (target: EtroObject, type: string, listener: <T extends Event>(T) => void): void {
50
+ if (!listeners.has(target)) {
51
+ listeners.set(target, [])
52
+ }
53
+
54
+ listeners.get(target).push(
55
+ { type: new TypeId(type), listener }
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Remove an event listener
61
+ *
62
+ * @param target - a etro object
63
+ * @param type - the id of the type (can contain subtypes, such as
64
+ * "type.subtype")
65
+ * @param listener
66
+ */
67
+ export function unsubscribe (target: EtroObject, listener: <T extends Event>(T) => void): void {
68
+ // Make sure `listener` has been added with `subscribe`.
69
+ if (!listeners.has(target) ||
70
+ !listeners.get(target).map(pair => pair.listener).includes(listener)) {
71
+ throw new Error('No matching event listener to remove')
72
+ }
73
+
74
+ const removed = listeners.get(target)
75
+ .filter(pair => pair.listener !== listener)
76
+ listeners.set(target, removed)
77
+ }
78
+
79
+ /**
80
+ * Emits an event to all listeners
81
+ *
82
+ * @param target - a etro object
83
+ * @param type - the id of the type (can contain subtypes, such as
84
+ * "type.subtype")
85
+ * @param event - any additional event data
86
+ */
87
+ export function publish (target: EtroObject, type: string, event: Record<string, unknown>): Event {
88
+ (event as unknown as Event).target = target; // could be a proxy
89
+ (event as unknown as Event).type = type
90
+
91
+ const t = new TypeId(type)
92
+
93
+ if (!listeners.has(target)) {
94
+ // No event fired
95
+ return null
96
+ }
97
+
98
+ // Call event listeners for this event.
99
+ const listenersForType = []
100
+ for (let i = 0; i < listeners.get(target).length; i++) {
101
+ const item = listeners.get(target)[i]
102
+ if (t.contains(item.type)) {
103
+ listenersForType.push(item.listener)
104
+ }
105
+ }
106
+
107
+ for (let i = 0; i < listenersForType.length; i++) {
108
+ const listener = listenersForType[i]
109
+ listener(event)
110
+ }
111
+
112
+ return event as unknown as Event
113
+ }
114
+
115
+ const listeners: WeakMap<EtroObject, {
116
+ type: TypeId,
117
+ listener: (Event) => void
118
+ }[]> = 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,217 @@
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
+ const time = movie.currentTime
99
+ if (time < this.startTime || time >= this.startTime + this.duration) {
100
+ return
101
+ }
102
+ this.source.currentTime = time - this.startTime
103
+ })
104
+
105
+ // TODO: on unattach?
106
+ subscribe(movie, 'movie.audiodestinationupdate', event => {
107
+ // Connect to new destination if immeidately connected to the existing
108
+ // destination.
109
+ if (this._connectedToDestination) {
110
+ this.audioNode.disconnect(movie.actx.destination)
111
+ this.audioNode.connect(event.destination)
112
+ }
113
+ })
114
+
115
+ // connect to audiocontext
116
+ this._audioNode = movie.actx.createMediaElementSource(this.source)
117
+
118
+ // Spy on connect and disconnect to remember if it connected to
119
+ // actx.destination (for Movie#record).
120
+ const oldConnect = this._audioNode.connect.bind(this.audioNode)
121
+ this._audioNode.connect = <T extends IAudioDestinationNode<AudioContext>>(destination: T, outputIndex?: number, inputIndex?: number): AudioNode => {
122
+ this._connectedToDestination = destination === movie.actx.destination
123
+ return oldConnect(destination, outputIndex, inputIndex)
124
+ }
125
+ const oldDisconnect = this._audioNode.disconnect.bind(this.audioNode)
126
+ this._audioNode.disconnect = <T extends IAudioDestinationNode<AudioContext>>(destination?: T | number, output?: number, input?: number): AudioNode => {
127
+ if (this._connectedToDestination &&
128
+ destination === movie.actx.destination) {
129
+ this._connectedToDestination = false
130
+ }
131
+ return oldDisconnect(destination, output, input)
132
+ }
133
+
134
+ // Connect to actx.destination by default (can be rewired by user)
135
+ this.audioNode.connect(movie.actx.destination)
136
+ }
137
+
138
+ start () {
139
+ this.source.currentTime = this.currentTime + this.sourceStartTime
140
+ this.source.play()
141
+ }
142
+
143
+ render () {
144
+ super.render()
145
+ // TODO: implement Issue: Create built-in audio node to support built-in
146
+ // audio nodes, as this does nothing rn
147
+ this.source.muted = val(this, 'muted', this.currentTime)
148
+ this.source.volume = val(this, 'volume', this.currentTime)
149
+ this.source.playbackRate = val(this, 'playbackRate', this.currentTime)
150
+ }
151
+
152
+ stop () {
153
+ this.source.pause()
154
+ }
155
+
156
+ /**
157
+ * The audio source node for the media
158
+ */
159
+ get audioNode () {
160
+ return this._audioNode
161
+ }
162
+
163
+ get playbackRate () {
164
+ return this._playbackRate
165
+ }
166
+
167
+ set playbackRate (value) {
168
+ this._playbackRate = value
169
+ if (this._unstretchedDuration !== undefined) {
170
+ this.duration = this._unstretchedDuration / value
171
+ }
172
+ }
173
+
174
+ get startTime () {
175
+ return this.__startTime
176
+ }
177
+
178
+ set startTime (val) {
179
+ this.__startTime = val
180
+ if (this._initialized) {
181
+ const mediaProgress = this.movie.currentTime - this.startTime
182
+ this.source.currentTime = this.sourceStartTime + mediaProgress
183
+ }
184
+ }
185
+
186
+ set sourceStartTime (val) {
187
+ this._sourceStartTime = val
188
+ if (this._initialized) {
189
+ const mediaProgress = this.movie.currentTime - this.startTime
190
+ this.source.currentTime = mediaProgress + this.sourceStartTime
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Time in the media at which the layer starts
196
+ */
197
+ get sourceStartTime () {
198
+ return this._sourceStartTime
199
+ }
200
+
201
+ getDefaultOptions (): MixedAudioSourceOptions {
202
+ return {
203
+ ...superclass.prototype.getDefaultOptions(),
204
+ source: undefined, // required
205
+ sourceStartTime: 0,
206
+ duration: undefined, // important to include undefined keys, for applyOptions
207
+ muted: false,
208
+ volume: 1,
209
+ playbackRate: 1
210
+ }
211
+ }
212
+ }
213
+
214
+ return MixedAudioSource
215
+ }
216
+
217
+ export { AudioSource, AudioSourceOptions, AudioSourceMixin }
@@ -0,0 +1,35 @@
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
+
22
+ getDefaultOptions (): AudioOptions {
23
+ return {
24
+ ...Object.getPrototypeOf(this).getDefaultOptions(),
25
+ /**
26
+ * @name module:layer.Audio#sourceStartTime
27
+ * @desc Where in the media to start playing when the layer starts
28
+ */
29
+ sourceStartTime: 0,
30
+ duration: undefined
31
+ }
32
+ }
33
+ }
34
+
35
+ export { Audio, AudioOptions }