etro 0.9.0 → 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.
Files changed (68) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/CONTRIBUTING.md +25 -34
  3. package/README.md +9 -17
  4. package/dist/custom-array.d.ts +10 -0
  5. package/dist/effect/base.d.ts +10 -1
  6. package/dist/effect/shader.d.ts +11 -1
  7. package/dist/effect/stack.d.ts +6 -2
  8. package/dist/etro-cjs.js +1182 -592
  9. package/dist/etro-iife.js +1182 -592
  10. package/dist/event.d.ts +10 -5
  11. package/dist/layer/audio-source.d.ts +9 -4
  12. package/dist/layer/audio.d.ts +15 -2
  13. package/dist/layer/base.d.ts +49 -3
  14. package/dist/layer/image.d.ts +15 -1
  15. package/dist/layer/text.d.ts +6 -3
  16. package/dist/layer/video.d.ts +13 -1
  17. package/dist/layer/visual-source.d.ts +18 -3
  18. package/dist/layer/visual.d.ts +11 -7
  19. package/dist/movie/effects.d.ts +6 -0
  20. package/dist/movie/index.d.ts +1 -0
  21. package/dist/movie/layers.d.ts +6 -0
  22. package/dist/movie/movie.d.ts +260 -0
  23. package/dist/object.d.ts +9 -2
  24. package/dist/util.d.ts +4 -10
  25. package/eslint.conf.js +4 -2
  26. package/eslint.test-conf.js +1 -2
  27. package/karma.conf.js +10 -14
  28. package/package.json +23 -22
  29. package/scripts/{gen-effect-samples.html → effect/gen-effect-samples.html} +24 -0
  30. package/scripts/{save-effect-samples.js → effect/save-effect-samples.js} +1 -1
  31. package/src/custom-array.ts +43 -0
  32. package/src/effect/base.ts +23 -22
  33. package/src/effect/gaussian-blur.ts +11 -6
  34. package/src/effect/pixelate.ts +3 -3
  35. package/src/effect/shader.ts +33 -27
  36. package/src/effect/stack.ts +43 -30
  37. package/src/effect/transform.ts +16 -9
  38. package/src/event.ts +111 -21
  39. package/src/layer/audio-source.ts +60 -20
  40. package/src/layer/audio.ts +25 -3
  41. package/src/layer/base.ts +79 -25
  42. package/src/layer/image.ts +26 -2
  43. package/src/layer/text.ts +11 -4
  44. package/src/layer/video.ts +31 -4
  45. package/src/layer/visual-source.ts +70 -8
  46. package/src/layer/visual.ts +57 -35
  47. package/src/movie/effects.ts +26 -0
  48. package/src/movie/index.ts +1 -0
  49. package/src/movie/layers.ts +26 -0
  50. package/src/movie/movie.ts +855 -0
  51. package/src/object.ts +9 -2
  52. package/src/util.ts +68 -89
  53. package/tsconfig.json +3 -1
  54. package/dist/movie.d.ts +0 -201
  55. package/examples/application/readme-screenshot.html +0 -85
  56. package/examples/application/video-player.html +0 -130
  57. package/examples/application/webcam.html +0 -28
  58. package/examples/introduction/audio.html +0 -64
  59. package/examples/introduction/effects.html +0 -79
  60. package/examples/introduction/export.html +0 -83
  61. package/examples/introduction/functions.html +0 -37
  62. package/examples/introduction/hello-world1.html +0 -37
  63. package/examples/introduction/hello-world2.html +0 -32
  64. package/examples/introduction/keyframes.html +0 -79
  65. package/examples/introduction/media.html +0 -63
  66. package/examples/introduction/text.html +0 -31
  67. package/private-todo.txt +0 -70
  68. package/src/movie.ts +0 -742
@@ -1,6 +1,40 @@
1
1
  import { Movie } from '../movie'
2
2
  import { Visual } from './visual'
3
3
  import { Visual as VisualLayer } from '../layer'
4
+ import { CustomArray, CustomArrayListener } from '../custom-array'
5
+
6
+ class StackEffectsListener extends CustomArrayListener<Visual> {
7
+ // eslint-disable-next-line no-use-before-define
8
+ private _stack: Stack
9
+
10
+ constructor (stack: Stack) {
11
+ super()
12
+ this._stack = stack
13
+ }
14
+
15
+ onAdd (effect: Visual) {
16
+ if (!this._stack.parent) {
17
+ return
18
+ }
19
+
20
+ effect.tryAttach(this._stack.parent)
21
+ }
22
+
23
+ onRemove (effect: Visual) {
24
+ if (!this._stack.parent) {
25
+ return
26
+ }
27
+
28
+ effect.tryDetach()
29
+ }
30
+ }
31
+
32
+ class StackEffects extends CustomArray<Visual> {
33
+ // eslint-disable-next-line no-use-before-define
34
+ constructor (target: Visual[], stack: Stack) {
35
+ super(target, new StackEffectsListener(stack))
36
+ }
37
+ }
4
38
 
5
39
  export interface StackOptions {
6
40
  effects: Visual[]
@@ -11,58 +45,37 @@ export interface StackOptions {
11
45
  * for defining reused effect sequences as one effect.
12
46
  */
13
47
  export class Stack extends Visual {
14
- readonly effects: Visual[]
15
-
16
- private _effectsBack: Visual[]
48
+ readonly effects: StackEffects
17
49
 
18
50
  constructor (options: StackOptions) {
19
51
  super()
20
52
 
21
- this._effectsBack = []
22
- // TODO: Throw 'change' events in handlers
23
- this.effects = new Proxy(this._effectsBack, {
24
- deleteProperty: function (target: Visual[], property: number | string): boolean {
25
- const value = target[property]
26
- value.detach() // Detach effect from movie
27
- delete target[property]
28
- return true
29
- },
30
- set: function (target: Visual[], property: number | string, value: Visual): boolean {
31
- // TODO: make sure type check works
32
- if (!isNaN(Number(property))) { // if property is a number (index)
33
- if (target[property])
34
- target[property].detach() // Detach old effect from movie
35
-
36
- value.attach(this._target) // Attach effect to movie
37
- }
38
- target[property] = value
39
- return true
40
- }
41
- })
53
+ this.effects = new StackEffects(options.effects, this)
42
54
  options.effects.forEach(effect => this.effects.push(effect))
43
-
44
- // TODO: Propogate 'change' events from children up
45
55
  }
46
56
 
47
57
  attach (movie: Movie): void {
48
58
  super.attach(movie)
59
+
49
60
  this.effects.filter(effect => !!effect).forEach(effect => {
50
- effect.detach()
51
- effect.attach(movie)
61
+ effect.tryAttach(movie)
52
62
  })
53
63
  }
54
64
 
55
65
  detach (): void {
56
66
  super.detach()
67
+
57
68
  this.effects.filter(effect => !!effect).forEach(effect => {
58
- effect.detach()
69
+ effect.tryDetach()
59
70
  })
60
71
  }
61
72
 
62
73
  apply (target: Movie | VisualLayer, reltime: number): void {
63
74
  for (let i = 0; i < this.effects.length; i++) {
64
75
  const effect = this.effects[i]
65
- if (!effect) continue
76
+ if (!effect) {
77
+ continue
78
+ }
66
79
  effect.apply(target, reltime)
67
80
  }
68
81
  }
@@ -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
 
@@ -36,11 +36,13 @@ class Transform extends Visual {
36
36
  }
37
37
 
38
38
  apply (target: Movie | VisualLayer, reltime: number): void {
39
- if (target.canvas.width !== this._tmpCanvas.width)
39
+ if (target.canvas.width !== this._tmpCanvas.width) {
40
40
  this._tmpCanvas.width = target.canvas.width
41
+ }
41
42
 
42
- if (target.canvas.height !== this._tmpCanvas.height)
43
+ if (target.canvas.height !== this._tmpCanvas.height) {
43
44
  this._tmpCanvas.height = target.canvas.height
45
+ }
44
46
 
45
47
  // Use data, since that's the underlying storage
46
48
  this._tmpMatrix.data = val(this, 'matrix.data', reltime)
@@ -81,8 +83,9 @@ namespace Transform { // eslint-disable-line @typescript-eslint/no-namespace
81
83
  }
82
84
 
83
85
  identity (): Matrix {
84
- for (let i = 0; i < this.data.length; i++)
86
+ for (let i = 0; i < this.data.length; i++) {
85
87
  this.data[i] = Matrix.IDENTITY.data[i]
88
+ }
86
89
 
87
90
  return this
88
91
  }
@@ -93,8 +96,9 @@ namespace Transform { // eslint-disable-line @typescript-eslint/no-namespace
93
96
  * @param [val]
94
97
  */
95
98
  cell (x: number, y: number, val?: number): number {
96
- if (val !== undefined)
99
+ if (val !== undefined) {
97
100
  this.data[3 * y + x] = val
101
+ }
98
102
 
99
103
  return this.data[3 * y + x]
100
104
  }
@@ -130,18 +134,21 @@ namespace Transform { // eslint-disable-line @typescript-eslint/no-namespace
130
134
  */
131
135
  multiply (other: Matrix): Matrix {
132
136
  // copy to temporary matrix to avoid modifying `this` while reading from it
133
- for (let x = 0; x < 3; x++)
137
+ for (let x = 0; x < 3; x++) {
134
138
  for (let y = 0; y < 3; y++) {
135
139
  let sum = 0
136
- for (let i = 0; i < 3; i++)
140
+ for (let i = 0; i < 3; i++) {
137
141
  sum += this.cell(x, i) * other.cell(i, y)
142
+ }
138
143
 
139
144
  Matrix._TMP_MATRIX.cell(x, y, sum)
140
145
  }
146
+ }
141
147
 
142
148
  // copy data from TMP_MATRIX to this
143
- for (let i = 0; i < Matrix._TMP_MATRIX.data.length; i++)
149
+ for (let i = 0; i < Matrix._TMP_MATRIX.data.length; i++) {
144
150
  this.data[i] = Matrix._TMP_MATRIX.data[i]
151
+ }
145
152
 
146
153
  return this
147
154
  }
package/src/event.ts CHANGED
@@ -4,6 +4,32 @@
4
4
 
5
5
  import EtroObject from './object'
6
6
 
7
+ class DeprecatedEvent {
8
+ replacement: string
9
+ message: string
10
+
11
+ constructor (replacement: string, message: string = undefined) {
12
+ this.replacement = replacement
13
+ this.message = message
14
+ }
15
+
16
+ toString () {
17
+ let str = ''
18
+
19
+ if (this.replacement) {
20
+ str += `Use ${this.replacement} instead.`
21
+ }
22
+
23
+ if (this.message) {
24
+ str += ` ${this.message}`
25
+ }
26
+
27
+ return str
28
+ }
29
+ }
30
+
31
+ const deprecatedEvents: Record<string, DeprecatedEvent> = {}
32
+
7
33
  export interface Event {
8
34
  target: EtroObject
9
35
  type: string
@@ -21,12 +47,15 @@ class TypeId {
21
47
  }
22
48
 
23
49
  contains (other) {
24
- if (other._parts.length > this._parts.length)
50
+ if (other._parts.length > this._parts.length) {
25
51
  return false
52
+ }
26
53
 
27
- for (let i = 0; i < other._parts.length; i++)
28
- if (other._parts[i] !== this._parts[i])
54
+ for (let i = 0; i < other._parts.length; i++) {
55
+ if (other._parts[i] !== this._parts[i]) {
29
56
  return false
57
+ }
58
+ }
30
59
 
31
60
  return true
32
61
  }
@@ -36,27 +65,60 @@ class TypeId {
36
65
  }
37
66
  }
38
67
 
68
+ export function deprecate (type: string, newType: string, message: string = undefined): void {
69
+ deprecatedEvents[type] = new DeprecatedEvent(newType, message)
70
+ }
71
+
72
+ function subscribeOnce (target: EtroObject, type: string, listener: <T extends Event>(T) => void): void {
73
+ const wrapped = event => {
74
+ unsubscribe(target, wrapped)
75
+ listener(event)
76
+ }
77
+ subscribe(target, type, wrapped)
78
+ }
79
+
80
+ function subscribeMany (target: EtroObject, type: string, listener: <T extends Event>(T) => void): void {
81
+ if (!listeners.has(target)) {
82
+ listeners.set(target, [])
83
+ }
84
+
85
+ listeners.get(target).push(
86
+ { type: new TypeId(type), listener }
87
+ )
88
+ }
89
+
39
90
  /**
40
- * Listen for an event or category of events
91
+ * Listen for an event or category of events.
41
92
  *
42
- * @param target - a etro object
93
+ * @param target - an etro object
43
94
  * @param type - the id of the type (can contain subtypes, such as
44
95
  * "type.subtype")
45
96
  * @param listener
97
+ * @param options - options
98
+ * @param options.once - if true, the listener will only be called once
46
99
  */
47
- export function subscribe (target: EtroObject, type: string, listener: <T extends Event>(T) => void): void {
48
- if (!listeners.has(target))
49
- listeners.set(target, [])
100
+ export function subscribe (
101
+ target: EtroObject,
102
+ type: string,
103
+ listener: <T extends Event>(T) => void,
104
+ options: { once?: boolean } = {}
105
+ ): void {
106
+ // Check if this event is deprecated.
107
+ if (Object.keys(deprecatedEvents).includes(type)) {
108
+ console.warn(`Event ${type} is deprecated. ${deprecatedEvents[type]}`)
109
+ }
50
110
 
51
- listeners.get(target).push(
52
- { type: new TypeId(type), listener }
53
- )
111
+ if (options.once) {
112
+ subscribeOnce(target, type, listener)
113
+ } else {
114
+ subscribeMany(target, type, listener)
115
+ }
54
116
  }
55
117
 
56
118
  /**
57
119
  * Remove an event listener
58
120
  *
59
- * @param target - a etro object
121
+ * @param target - an etro object
60
122
  * @param type - the id of the type (can contain subtypes, such as
61
123
  * "type.subtype")
62
124
  * @param listener
@@ -64,8 +126,9 @@ export function subscribe (target: EtroObject, type: string, listener: <T extend
64
126
  export function unsubscribe (target: EtroObject, listener: <T extends Event>(T) => void): void {
65
127
  // Make sure `listener` has been added with `subscribe`.
66
128
  if (!listeners.has(target) ||
67
- !listeners.get(target).map(pair => pair.listener).includes(listener))
129
+ !listeners.get(target).map(pair => pair.listener).includes(listener)) {
68
130
  throw new Error('No matching event listener to remove')
131
+ }
69
132
 
70
133
  const removed = listeners.get(target)
71
134
  .filter(pair => pair.listener !== listener)
@@ -73,29 +136,31 @@ export function unsubscribe (target: EtroObject, listener: <T extends Event>(T)
73
136
  }
74
137
 
75
138
  /**
76
- * Emits an event to all listeners
139
+ * Publish an event to all listeners without checking if it is deprecated.
77
140
  *
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
141
+ * @param target
142
+ * @param type
143
+ * @param event
144
+ * @returns
82
145
  */
83
- export function publish (target: EtroObject, type: string, event: Record<string, unknown>): Event {
146
+ function _publish (target: EtroObject, type: string, event: Record<string, unknown>): Event {
84
147
  (event as unknown as Event).target = target; // could be a proxy
85
148
  (event as unknown as Event).type = type
86
149
 
87
150
  const t = new TypeId(type)
88
151
 
89
- if (!listeners.has(target))
152
+ if (!listeners.has(target)) {
90
153
  // No event fired
91
154
  return null
155
+ }
92
156
 
93
157
  // Call event listeners for this event.
94
158
  const listenersForType = []
95
159
  for (let i = 0; i < listeners.get(target).length; i++) {
96
160
  const item = listeners.get(target)[i]
97
- if (t.contains(item.type))
161
+ if (t.contains(item.type)) {
98
162
  listenersForType.push(item.listener)
163
+ }
99
164
  }
100
165
 
101
166
  for (let i = 0; i < listenersForType.length; i++) {
@@ -106,6 +171,31 @@ export function publish (target: EtroObject, type: string, event: Record<string,
106
171
  return event as unknown as Event
107
172
  }
108
173
 
174
+ /**
175
+ * Emits an event to all listeners
176
+ *
177
+ * @param target - an etro object
178
+ * @param type - the id of the type (can contain subtypes, such as
179
+ * "type.subtype")
180
+ * @param event - any additional event data
181
+ */
182
+ export function publish (target: EtroObject, type: string, event: Record<string, unknown>): Event {
183
+ // Check if this event is deprecated only if it can be replaced.
184
+ if (Object.keys(deprecatedEvents).includes(type) && deprecatedEvents[type].replacement) {
185
+ throw new Error(`Event ${type} is deprecated. ${deprecatedEvents[type]}`)
186
+ }
187
+
188
+ // Check for deprecated events that this event replaces.
189
+ for (const deprecated in deprecatedEvents) {
190
+ const deprecatedEvent = deprecatedEvents[deprecated]
191
+ if (type === deprecatedEvent.replacement) {
192
+ _publish(target, deprecated, { ...event })
193
+ }
194
+ }
195
+
196
+ return _publish(target, type, event)
197
+ }
198
+
109
199
  const listeners: WeakMap<EtroObject, {
110
200
  type: TypeId,
111
201
  listener: (Event) => void
@@ -6,19 +6,24 @@ import { Base, BaseOptions } from './base'
6
6
  type Constructor<T> = new (...args: unknown[]) => T
7
7
 
8
8
  interface AudioSource extends Base {
9
- readonly source: HTMLMediaElement
9
+ /** HTML media element (an audio or video element) */
10
+ readonly source: HTMLAudioElement
11
+ /** Audio source node for the media */
10
12
  readonly audioNode: AudioNode
11
13
  playbackRate: number
12
- /** The audio source node for the media */
14
+ /** Seconds to skip ahead by */
13
15
  sourceStartTime: number
14
16
  }
15
17
 
16
- interface AudioSourceOptions extends BaseOptions {
18
+ interface AudioSourceOptions extends Omit<BaseOptions, 'duration'> {
19
+ duration?: number
20
+ /** HTML media element (an audio or video element) */
17
21
  source: HTMLMediaElement
22
+ /** Seconds to skip ahead by */
18
23
  sourceStartTime?: number
19
24
  muted?: boolean
20
25
  volume?: number
21
- playbackRate: number
26
+ playbackRate?: number
22
27
  onload?: (source: HTMLMediaElement, options: AudioSourceOptions) => void
23
28
  }
24
29
 
@@ -59,18 +64,31 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
59
64
  * @param [options.playbackRate=1]
60
65
  */
61
66
  constructor (options: MixedAudioSourceOptions) {
67
+ if (!options.source) {
68
+ throw new Error('Property "source" is required in options')
69
+ }
70
+
62
71
  const onload = options.onload
63
72
  // Don't set as instance property
64
73
  delete options.onload
65
- super(options)
74
+
75
+ super({
76
+ ...options,
77
+
78
+ // Set a default duration so that the super constructor doesn't throw an
79
+ // error
80
+ duration: options.duration ?? 0
81
+ })
82
+
66
83
  this._initialized = false
67
84
  this._sourceStartTime = options.sourceStartTime || 0
68
85
  applyOptions(options, this)
69
86
 
70
87
  const load = () => {
71
88
  // TODO: && ?
72
- if ((options.duration || (this.source.duration - this.sourceStartTime)) < 0)
89
+ if ((options.duration || (this.source.duration - this.sourceStartTime)) < 0) {
73
90
  throw new Error('Invalid options.duration or options.sourceStartTime')
91
+ }
74
92
 
75
93
  this._unstretchedDuration = options.duration || (this.source.duration - this.sourceStartTime)
76
94
  this.duration = this._unstretchedDuration / (this.playbackRate)
@@ -78,31 +96,34 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
78
96
  // super()
79
97
  onload && onload.bind(this)(this.source, options)
80
98
  }
81
- if (this.source.readyState >= 2)
99
+ if (this.source.readyState >= 2) {
82
100
  // this frame's data is available now
83
101
  load()
84
- else
102
+ } else {
85
103
  // when this frame's data is available
86
104
  this.source.addEventListener('loadedmetadata', load)
105
+ }
87
106
 
88
107
  this.source.addEventListener('durationchange', () => {
89
108
  this.duration = options.duration || (this.source.duration - this.sourceStartTime)
90
109
  })
91
110
  }
92
111
 
112
+ async whenReady (): Promise<void> {
113
+ await super.whenReady()
114
+ if (this.source.readyState < 4) {
115
+ await new Promise(resolve => {
116
+ this.source.addEventListener('canplaythrough', resolve)
117
+ })
118
+ }
119
+ }
120
+
93
121
  attach (movie: Movie) {
94
122
  super.attach(movie)
95
123
 
96
- subscribe(movie, 'movie.seek', () => {
97
- if (this.currentTime < 0 || this.currentTime >= this.duration)
98
- return
99
-
100
- this.source.currentTime = this.currentTime + this.sourceStartTime
101
- })
102
-
103
124
  // TODO: on unattach?
104
- subscribe(movie, 'movie.audiodestinationupdate', event => {
105
- // Connect to new destination if immeidately connected to the existing
125
+ subscribe(movie, 'audiodestinationupdate', event => {
126
+ // Connect to new destination if immediately connected to the existing
106
127
  // destination.
107
128
  if (this._connectedToDestination) {
108
129
  this.audioNode.disconnect(movie.actx.destination)
@@ -123,8 +144,9 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
123
144
  const oldDisconnect = this._audioNode.disconnect.bind(this.audioNode)
124
145
  this._audioNode.disconnect = <T extends AudioDestinationNode>(destination?: T | number, output?: number, input?: number): AudioNode => {
125
146
  if (this._connectedToDestination &&
126
- destination === movie.actx.destination)
147
+ destination === movie.actx.destination) {
127
148
  this._connectedToDestination = false
149
+ }
128
150
 
129
151
  return oldDisconnect(destination, output, input)
130
152
  }
@@ -145,6 +167,12 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
145
167
  this.source.play()
146
168
  }
147
169
 
170
+ seek (time: number): void {
171
+ super.seek(time)
172
+
173
+ this.source.currentTime = this.currentTime + this.sourceStartTime
174
+ }
175
+
148
176
  render () {
149
177
  super.render()
150
178
  // TODO: implement Issue: Create built-in audio node to support built-in
@@ -155,6 +183,8 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
155
183
  }
156
184
 
157
185
  stop () {
186
+ super.stop()
187
+
158
188
  this.source.pause()
159
189
  }
160
190
 
@@ -171,8 +201,9 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
171
201
 
172
202
  set playbackRate (value) {
173
203
  this._playbackRate = value
174
- if (this._unstretchedDuration !== undefined)
204
+ if (this._unstretchedDuration !== undefined) {
175
205
  this.duration = this._unstretchedDuration / value
206
+ }
176
207
  }
177
208
 
178
209
  get startTime () {
@@ -202,7 +233,16 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
202
233
  return this._sourceStartTime
203
234
  }
204
235
 
205
- getDefaultOptions (): MixedAudioSourceOptions {
236
+ get ready (): boolean {
237
+ // Typescript doesn't support `super.ready` when targeting es5
238
+ const superReady = Object.getOwnPropertyDescriptor(superclass.prototype, 'ready').get.call(this)
239
+ return superReady && this.source.readyState === 4
240
+ }
241
+
242
+ /**
243
+ * @deprecated See {@link https://github.com/etro-js/etro/issues/131}
244
+ */
245
+ getDefaultOptions () {
206
246
  return {
207
247
  ...superclass.prototype.getDefaultOptions(),
208
248
  source: undefined, // required
@@ -3,22 +3,44 @@
3
3
  import { Base, BaseOptions } from './base'
4
4
  import { AudioSourceMixin, AudioSourceOptions } from './audio-source'
5
5
 
6
- type AudioOptions = AudioSourceOptions
6
+ interface AudioOptions extends Omit<AudioSourceOptions, 'source'> {
7
+ /**
8
+ * The raw html `<audio>` element
9
+ */
10
+ source: string | HTMLAudioElement
11
+ }
7
12
 
8
13
  /**
14
+ * Layer for an HTML audio element
9
15
  * @extends AudioSource
10
16
  */
11
17
  class Audio extends AudioSourceMixin<BaseOptions>(Base) {
18
+ /**
19
+ * The raw html `<audio>` element
20
+ */
21
+ source: HTMLAudioElement
22
+
12
23
  /**
13
24
  * Creates an audio layer
14
25
  */
15
26
  constructor (options: AudioOptions) {
27
+ if (typeof options.source === 'string') {
28
+ const audio = document.createElement('audio')
29
+ audio.src = options.source
30
+ options.source = audio
31
+ }
32
+
16
33
  super(options)
17
- if (this.duration === undefined)
34
+
35
+ if (this.duration === undefined) {
18
36
  this.duration = (this).source.duration - this.sourceStartTime
37
+ }
19
38
  }
20
39
 
21
- getDefaultOptions (): AudioOptions {
40
+ /**
41
+ * @deprecated See {@link https://github.com/etro-js/etro/issues/131}
42
+ */
43
+ getDefaultOptions () {
22
44
  return {
23
45
  ...Object.getPrototypeOf(this).getDefaultOptions(),
24
46
  /**