@editframe/elements 0.11.0-beta.9 → 0.12.0-beta.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 (47) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +8 -15
  2. package/dist/elements/EFCaptions.d.ts +50 -6
  3. package/dist/elements/EFMedia.d.ts +1 -1
  4. package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
  5. package/dist/elements/EFTimegroup.d.ts +23 -2
  6. package/dist/elements/EFWaveform.d.ts +15 -11
  7. package/dist/elements/src/EF_FRAMEGEN.js +24 -26
  8. package/dist/elements/src/elements/EFCaptions.js +295 -42
  9. package/dist/elements/src/elements/EFImage.js +0 -6
  10. package/dist/elements/src/elements/EFMedia.js +0 -5
  11. package/dist/elements/src/elements/EFTemporal.js +13 -10
  12. package/dist/elements/src/elements/EFTimegroup.js +37 -12
  13. package/dist/elements/src/elements/EFVideo.js +1 -4
  14. package/dist/elements/src/elements/EFWaveform.js +250 -143
  15. package/dist/elements/src/gui/ContextMixin.js +36 -7
  16. package/dist/elements/src/gui/EFScrubber.js +142 -0
  17. package/dist/elements/src/gui/EFTimeDisplay.js +81 -0
  18. package/dist/elements/src/gui/EFTogglePlay.js +14 -14
  19. package/dist/elements/src/gui/EFWorkbench.js +1 -24
  20. package/dist/elements/src/gui/TWMixin.css.js +1 -1
  21. package/dist/elements/src/index.js +8 -1
  22. package/dist/gui/ContextMixin.d.ts +2 -1
  23. package/dist/gui/EFScrubber.d.ts +23 -0
  24. package/dist/gui/EFTimeDisplay.d.ts +17 -0
  25. package/dist/gui/EFTogglePlay.d.ts +1 -1
  26. package/dist/gui/EFWorkbench.d.ts +0 -1
  27. package/dist/index.d.ts +3 -1
  28. package/dist/style.css +6 -801
  29. package/package.json +2 -2
  30. package/src/elements/EFCaptions.browsertest.ts +6 -6
  31. package/src/elements/EFCaptions.ts +325 -56
  32. package/src/elements/EFImage.browsertest.ts +4 -17
  33. package/src/elements/EFImage.ts +0 -6
  34. package/src/elements/EFMedia.browsertest.ts +8 -19
  35. package/src/elements/EFMedia.ts +1 -6
  36. package/src/elements/EFTemporal.browsertest.ts +14 -0
  37. package/src/elements/EFTemporal.ts +14 -0
  38. package/src/elements/EFTimegroup.browsertest.ts +37 -0
  39. package/src/elements/EFTimegroup.ts +42 -17
  40. package/src/elements/EFVideo.ts +1 -4
  41. package/src/elements/EFWaveform.ts +339 -314
  42. package/src/gui/ContextMixin.browsertest.ts +28 -2
  43. package/src/gui/ContextMixin.ts +41 -9
  44. package/src/gui/EFScrubber.ts +145 -0
  45. package/src/gui/EFTimeDisplay.ts +81 -0
  46. package/src/gui/EFTogglePlay.ts +21 -21
  47. package/src/gui/EFWorkbench.ts +3 -36
@@ -1,6 +1,5 @@
1
1
  import { EFAudio } from "./EFAudio.js";
2
2
  import { Task } from "@lit/task";
3
- import * as d3 from "d3";
4
3
  import { html, css, LitElement } from "lit";
5
4
  import { property, customElement } from "lit/decorators.js";
6
5
  import { createRef, ref } from "lit/directives/ref.js";
@@ -22,15 +21,86 @@ var __decorateClass = (decorators, target, key, kind) => {
22
21
  let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
23
22
  constructor() {
24
23
  super(...arguments);
25
- this.svgRef = createRef();
24
+ this.canvasRef = createRef();
25
+ this.ctx = null;
26
26
  this.mode = "bars";
27
27
  this.color = "currentColor";
28
28
  this.targetSelector = "";
29
+ this.lastFrameTime = 0;
29
30
  this.frameTask = new Task(this, {
30
31
  autoRun: EF_INTERACTIVE,
31
32
  args: () => [this.targetElement.audioBufferTask.status],
32
33
  task: async () => {
34
+ const currentTime = performance.now();
35
+ const timeSinceLastFrame = this.lastFrameTime ? currentTime - this.lastFrameTime : 0;
36
+ console.log(`Time since last frame: ${timeSinceLastFrame.toFixed(2)}ms`);
37
+ this.lastFrameTime = currentTime;
33
38
  await this.targetElement.audioBufferTask.taskComplete;
39
+ this.ctx ||= this.initCanvas();
40
+ const ctx = this.ctx;
41
+ if (!ctx) return;
42
+ if (!this.targetElement.audioBufferTask.value) return;
43
+ if (this.targetElement.trimAdjustedOwnCurrentTimeMs > 0) {
44
+ const FRAMES_TO_ANALYZE = 4;
45
+ const FRAME_DURATION_MS = 48e3 / 100;
46
+ const multiFrameData = [];
47
+ for (let i = 0; i < FRAMES_TO_ANALYZE; i++) {
48
+ const frameOffset = i - Math.floor(FRAMES_TO_ANALYZE / 2);
49
+ const audioContext = new OfflineAudioContext(2, 48e3 / 25, 48e3);
50
+ const audioBufferSource = audioContext.createBufferSource();
51
+ audioBufferSource.buffer = this.targetElement.audioBufferTask.value.buffer;
52
+ const analyser = audioContext.createAnalyser();
53
+ analyser.fftSize = 128 / 2;
54
+ analyser.smoothingTimeConstant = 0.1;
55
+ audioBufferSource.connect(analyser);
56
+ const startTime = Math.max(
57
+ 0,
58
+ (this.targetElement.trimAdjustedOwnCurrentTimeMs - this.targetElement.audioBufferTask.value.startOffsetMs) / 1e3 + frameOffset * FRAME_DURATION_MS / 1e3
59
+ );
60
+ audioBufferSource.start(0, startTime, FRAME_DURATION_MS / 1e3);
61
+ await audioContext.startRendering();
62
+ const frameData = new Uint8Array(analyser.frequencyBinCount);
63
+ analyser.getByteFrequencyData(frameData);
64
+ multiFrameData.push(frameData);
65
+ }
66
+ const dataLength = multiFrameData[0]?.length ?? 0;
67
+ const smoothedData = new Uint8Array(dataLength);
68
+ for (let i = 0; i < smoothedData.length; i++) {
69
+ let sum = 0;
70
+ for (const frameData of multiFrameData) {
71
+ sum += frameData[i] ?? 0;
72
+ }
73
+ let avg = sum / FRAMES_TO_ANALYZE;
74
+ avg = (avg / 255) ** 1.2 * 255;
75
+ smoothedData[i] = Math.round(avg);
76
+ }
77
+ switch (this.mode) {
78
+ case "bars":
79
+ this.drawBars(ctx, smoothedData);
80
+ break;
81
+ case "bricks":
82
+ this.drawBricks(ctx, smoothedData);
83
+ break;
84
+ case "curve":
85
+ this.drawCurve(ctx, smoothedData);
86
+ break;
87
+ case "line":
88
+ this.drawLine(ctx, smoothedData);
89
+ break;
90
+ case "pixel":
91
+ this.drawPixel(ctx, smoothedData);
92
+ break;
93
+ case "wave":
94
+ this.drawWave(ctx, smoothedData);
95
+ break;
96
+ case "roundBars":
97
+ this.drawRoundBars(ctx, smoothedData);
98
+ break;
99
+ case "equalizer":
100
+ this.drawEqualizer(ctx, smoothedData);
101
+ break;
102
+ }
103
+ }
34
104
  }
35
105
  });
36
106
  }
@@ -38,7 +108,7 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
38
108
  return this;
39
109
  }
40
110
  render() {
41
- return html` <svg ${ref(this.svgRef)} store></svg> `;
111
+ return html`<canvas ${ref(this.canvasRef)}></canvas>`;
42
112
  }
43
113
  set target(value) {
44
114
  this.targetSelector = value;
@@ -49,153 +119,183 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
49
119
  new CrossUpdateController(this.targetElement, this);
50
120
  }
51
121
  }
52
- drawBars(svg, frequencyData) {
53
- const waveWidth = svg.clientWidth * devicePixelRatio;
54
- const waveHeight = svg.clientHeight;
55
- const waveLeft = 0;
56
- const waveRight = waveWidth;
57
- const barX = d3.scaleBand().paddingInner(0.5).paddingOuter(0.01).domain(d3.range(frequencyData.length).map((n) => String(n))).rangeRound([waveLeft, waveRight]);
58
- const height = d3.scaleLinear().domain([0, 255]).range([0, waveHeight / 2]);
122
+ initCanvas() {
123
+ console.count("initCanvas");
124
+ const canvas = this.canvasRef.value;
125
+ if (!canvas) return null;
126
+ const rect = this.getBoundingClientRect();
127
+ const dpr = window.devicePixelRatio;
128
+ canvas.style.width = `${rect.width}px`;
129
+ canvas.style.height = `${rect.height}px`;
130
+ canvas.width = rect.width * dpr;
131
+ canvas.height = rect.height * dpr;
132
+ const ctx = canvas.getContext("2d");
133
+ if (!ctx) return null;
134
+ ctx.scale(dpr, dpr);
135
+ return ctx;
136
+ }
137
+ drawBars(ctx, frequencyData) {
138
+ ctx.strokeStyle = this.color;
139
+ ctx.fillStyle = this.color;
140
+ const canvas = ctx.canvas;
141
+ const waveWidth = canvas.width / devicePixelRatio;
142
+ const waveHeight = canvas.height / devicePixelRatio;
143
+ const barWidth = waveWidth / frequencyData.length * 0.8;
59
144
  const baseline = waveHeight / 2;
60
- const bars = d3.select(svg).selectAll("rect").data(frequencyData);
61
- const minBarHeight = 2;
62
- bars.enter().append("rect").merge(bars).attr("x", (_, i) => barX(String(i)) || 0).attr("y", (value) => baseline - height(value)).attr("width", barX.bandwidth() / 1.2).attr("height", (value) => Math.max(height(value) * 2, minBarHeight));
63
- bars.exit().remove();
64
- }
65
- drawBricks(svg, frequencyData) {
66
- const waveWidth = svg.clientWidth * devicePixelRatio;
67
- const waveHeight = svg.clientHeight * devicePixelRatio;
145
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
146
+ frequencyData.forEach((value, i) => {
147
+ const height = value / 255 * (waveHeight / 2);
148
+ const x = i * (waveWidth / frequencyData.length);
149
+ const y = baseline - height;
150
+ ctx.fillRect(x, y, barWidth, Math.max(height * 2, 2));
151
+ });
152
+ }
153
+ drawBricks(ctx, frequencyData) {
154
+ ctx.strokeStyle = this.color;
155
+ ctx.fillStyle = this.color;
156
+ const canvas = ctx.canvas;
157
+ const waveWidth = canvas.width / devicePixelRatio;
158
+ const waveHeight = canvas.height / devicePixelRatio;
68
159
  const brickWidth = waveWidth / frequencyData.length;
69
160
  const brickHeightFactor = waveHeight / 255 / 2;
70
161
  const brickPadding = 2;
71
162
  const midHeight = waveHeight / 2;
72
- d3.select(svg).selectAll("line.brickBaseLine").data([0]).join("line").attr("class", "brickBaseLine").attr("x1", 0).attr("x2", waveWidth).attr("y1", midHeight).attr("y2", midHeight).attr("stroke", "currentColor").attr("stroke-width", 4).attr("stroke-dasharray", "2");
73
- d3.select(svg).selectAll("rect.brick").data(frequencyData).join("rect").attr("class", "brick").attr("x", (_d, i) => i * brickWidth).attr("y", (d) => midHeight - d * brickHeightFactor).attr("width", brickWidth - brickPadding).attr("height", (d) => d * brickHeightFactor * 2);
74
- }
75
- drawLine(svg, frequencyData) {
76
- const waveWidth = svg.clientWidth * devicePixelRatio;
77
- const waveHeight = svg.clientHeight * devicePixelRatio;
78
- const xScale = d3.scaleLinear().domain([0, frequencyData.length - 1]).range([0, waveWidth]);
79
- const yScale = d3.scaleLinear().domain([0, 255]).range([waveHeight, 0]);
80
- const lineGenerator = d3.line().x((_, i) => xScale(i)).y((d) => yScale(d));
81
- const pathData = lineGenerator(frequencyData);
82
- d3.select(svg).selectAll("path.line").data([frequencyData]).join("path").attr("class", "line").attr("d", pathData).attr("fill", "none").attr("stroke", "currentColor").attr("stroke-width", 4);
83
- }
84
- drawRoundBars(svg, frequencyData) {
85
- const waveWidth = svg.clientWidth * devicePixelRatio;
86
- const waveHeight = svg.clientHeight;
87
- const waveLeft = 0;
88
- const waveRight = waveWidth;
89
- const barX = d3.scaleBand().paddingInner(0.5).paddingOuter(0.01).domain(d3.range(frequencyData.length).map((n) => String(n))).rangeRound([waveLeft, waveRight]);
90
- const height = d3.scaleLinear().domain([0, 255]).range([0, waveHeight / 2]);
163
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
164
+ ctx.beginPath();
165
+ ctx.setLineDash([2]);
166
+ ctx.lineWidth = 4;
167
+ ctx.moveTo(0, midHeight);
168
+ ctx.lineTo(waveWidth, midHeight);
169
+ ctx.stroke();
170
+ ctx.setLineDash([]);
171
+ frequencyData.forEach((value, i) => {
172
+ const x = i * brickWidth;
173
+ const height = value * brickHeightFactor * 2;
174
+ const y = midHeight - height / 2;
175
+ ctx.fillRect(x, y, brickWidth - brickPadding, height);
176
+ });
177
+ }
178
+ drawLine(ctx, frequencyData) {
179
+ ctx.strokeStyle = this.color;
180
+ ctx.fillStyle = this.color;
181
+ const canvas = ctx.canvas;
182
+ const waveWidth = canvas.width / devicePixelRatio;
183
+ const waveHeight = canvas.height / devicePixelRatio;
184
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
185
+ ctx.beginPath();
186
+ frequencyData.forEach((value, i) => {
187
+ const x = i / frequencyData.length * waveWidth;
188
+ const y = (1 - value / 255) * waveHeight;
189
+ if (i === 0) {
190
+ ctx.moveTo(x, y);
191
+ } else {
192
+ ctx.lineTo(x, y);
193
+ }
194
+ });
195
+ ctx.lineWidth = 4;
196
+ ctx.stroke();
197
+ }
198
+ drawRoundBars(ctx, frequencyData) {
199
+ ctx.strokeStyle = this.color;
200
+ ctx.fillStyle = this.color;
201
+ const canvas = ctx.canvas;
202
+ const waveWidth = canvas.width / devicePixelRatio;
203
+ const waveHeight = canvas.height / devicePixelRatio;
204
+ const barWidth = waveWidth / frequencyData.length * 0.5;
91
205
  const baseline = waveHeight / 2;
92
- const bars = d3.select(svg).selectAll("rect").data(frequencyData);
93
- const minBarHeight = 2;
94
- bars.enter().append("rect").merge(bars).attr("x", (_, i) => barX(String(i)) || 0).attr("y", (value) => baseline - height(value)).attr("width", barX.bandwidth() / 1.2).attr("height", (value) => Math.max(height(value) * 2, minBarHeight)).attr("rx", barX.bandwidth()).attr("ry", barX.bandwidth());
95
- bars.exit().remove();
96
- }
97
- drawEqualizer(svg, frequencyData) {
98
- const waveWidth = svg.clientWidth * devicePixelRatio;
99
- const waveHeight = svg.clientHeight * devicePixelRatio;
100
- const barWidth = waveWidth / frequencyData.length;
101
- const barPadding = 1;
102
- const minHeight = 1;
103
- const heightScale = d3.scaleLinear().domain([0, 255]).range([0, waveHeight / 2]);
104
- d3.select(svg).selectAll("line.equalizerBaseLine").data([0]).join("line").attr("class", "equalizerBaseLine").attr("x1", 0).attr("x2", waveWidth).attr("y1", waveHeight / 2).attr("y2", waveHeight / 2).attr("stroke-width", 2);
105
- d3.select(svg).selectAll("rect.equalizerBar").data(frequencyData).join("rect").attr("class", "equalizerBar").attr("x", (_d, i) => i * barWidth + barPadding / 2).attr(
106
- "y",
107
- (d) => waveHeight / 2 - Math.max(heightScale(d) / 2, minHeight)
108
- ).attr("width", barWidth - barPadding).attr("height", (d) => Math.max(heightScale(d), minHeight));
109
- d3.select(svg).selectAll("rect.equalizerBar").transition().duration(100).attr(
110
- "y",
111
- (d) => waveHeight / 2 - Math.max(heightScale(Number(d)) / 2, minHeight)
112
- ).attr("height", (d) => Math.max(heightScale(Number(d)), minHeight));
113
- }
114
- drawCurve(svg, frequencyData) {
115
- const waveWidth = svg.clientWidth * devicePixelRatio;
116
- const waveHeight = svg.clientHeight * devicePixelRatio;
117
- const xScale = d3.scaleLinear().domain([0, frequencyData.length]).range([0, waveWidth]);
118
- const yScale = d3.scaleLinear().domain([0, 255]).range([waveHeight, 0]);
119
- const curveGenerator = d3.line().x((_, i) => xScale(i)).y((d) => yScale(d)).curve(d3.curveNatural);
120
- const pathData = curveGenerator(frequencyData);
121
- d3.select(svg).selectAll("path.curve").data([frequencyData]).join("path").attr("class", "curve").attr("d", pathData).attr("fill", "none").attr("stroke", "currentColor").attr("stroke-width", 4);
122
- }
123
- drawPixel(svg, frequencyData) {
124
- const waveWidth = svg.clientWidth * devicePixelRatio;
125
- const waveHeight = svg.clientHeight;
206
+ const maxHeight = waveHeight / 2;
207
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
208
+ frequencyData.forEach((value, i) => {
209
+ const height = value / 255 * maxHeight;
210
+ const x = i * (waveWidth / frequencyData.length);
211
+ const y = baseline - height;
212
+ const radius = barWidth / 2;
213
+ ctx.beginPath();
214
+ ctx.roundRect(x, y, barWidth, Math.max(height * 2, 2), radius);
215
+ ctx.fill();
216
+ });
217
+ }
218
+ drawEqualizer(ctx, frequencyData) {
219
+ ctx.strokeStyle = this.color;
220
+ ctx.fillStyle = this.color;
221
+ const canvas = ctx.canvas;
222
+ const waveWidth = canvas.width / devicePixelRatio;
223
+ const waveHeight = canvas.height / devicePixelRatio;
224
+ const barWidth = waveWidth / frequencyData.length * 0.8;
126
225
  const baseline = waveHeight / 2;
127
- const barX = d3.scaleBand().domain(d3.range(frequencyData.length).map(String)).rangeRound([0, waveWidth]).paddingInner(0.03).paddingOuter(0.02);
128
- const height = d3.scaleLinear().domain([0, 255]).range([0, baseline]);
129
- const bars = d3.select(svg).selectAll("rect").data(frequencyData);
130
- bars.enter().append("rect").merge(bars).attr("x", (_, i) => barX(String(i)) || 0).attr("y", (value) => baseline - height(value)).attr("width", barX.bandwidth()).attr("height", (value) => height(value) * 2);
131
- bars.exit().remove();
132
- }
133
- drawWave(svg, frequencyData) {
134
- const waveWidth = svg.clientWidth * devicePixelRatio;
135
- const waveHeight = svg.clientHeight;
136
- const barX = d3.scaleBand().domain(d3.range(frequencyData.length).map(String)).rangeRound([0, waveWidth]).paddingInner(0.03).paddingOuter(0.02);
137
- const height = d3.scaleLinear().domain([0, 255]).range([0, waveHeight / 2]);
138
- d3.select(svg).selectAll("line.baseline").data([0]).join("line").attr("class", "baseline").attr("x1", (_, i) => barX(String(i)) || 0).attr("x2", waveWidth).attr("y1", waveHeight / 2).attr("y2", waveHeight / 2).attr("stroke", "currentColor").attr("stroke-width", 2);
139
- const bars = d3.select(svg).selectAll("rect").data(frequencyData);
140
- bars.enter().append("rect").merge(bars).attr("x", (_, i) => barX(String(i)) || 0).attr("y", (value) => waveHeight / 2 - height(value)).attr("width", barX.bandwidth()).attr("height", (value) => height(value) * 2);
141
- bars.exit().remove();
142
- }
143
- async updated() {
144
- const svg = this.svgRef.value;
145
- if (!svg) {
146
- return;
147
- }
148
- if (!this.targetElement.audioBufferTask.value) {
149
- return;
150
- }
151
- if (this.targetElement.trimAdjustedOwnCurrentTimeMs > 0) {
152
- const audioContext = new OfflineAudioContext(2, 48e3 / 25, 48e3);
153
- const audioBufferSource = audioContext.createBufferSource();
154
- audioBufferSource.buffer = this.targetElement.audioBufferTask.value.buffer;
155
- const analyser = audioContext.createAnalyser();
156
- analyser.fftSize = 256;
157
- audioBufferSource.connect(analyser);
158
- audioBufferSource.start(
159
- 0,
160
- Math.max(
161
- 0,
162
- (this.targetElement.trimAdjustedOwnCurrentTimeMs - this.targetElement.audioBufferTask.value.startOffsetMs) / 1e3
163
- ),
164
- 48e3 / 1e3
165
- );
166
- await audioContext.startRendering();
167
- const frequencyData = new Uint8Array(analyser.frequencyBinCount);
168
- analyser.getByteFrequencyData(frequencyData);
169
- const rect = this.getBoundingClientRect();
170
- svg.setAttribute("width", (rect.width * devicePixelRatio).toString());
171
- svg.setAttribute("height", (rect.height * devicePixelRatio).toString());
172
- switch (this.mode) {
173
- case "bars":
174
- this.drawBars(svg, frequencyData);
175
- break;
176
- case "bricks":
177
- this.drawBricks(svg, frequencyData);
178
- break;
179
- case "curve":
180
- this.drawCurve(svg, frequencyData);
181
- break;
182
- case "line":
183
- this.drawLine(svg, frequencyData);
184
- break;
185
- case "pixel":
186
- this.drawPixel(svg, frequencyData);
187
- break;
188
- case "wave":
189
- this.drawWave(svg, frequencyData);
190
- break;
191
- case "roundBars":
192
- this.drawRoundBars(svg, frequencyData);
193
- break;
194
- case "equalizer":
195
- this.drawEqualizer(svg, frequencyData);
196
- break;
226
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
227
+ ctx.beginPath();
228
+ ctx.lineWidth = 2;
229
+ ctx.moveTo(0, baseline);
230
+ ctx.lineTo(waveWidth, baseline);
231
+ ctx.stroke();
232
+ frequencyData.forEach((value, i) => {
233
+ const height = value / 255 * (waveHeight / 2);
234
+ const x = i * (waveWidth / frequencyData.length);
235
+ const y = baseline - height;
236
+ ctx.fillRect(x, y, barWidth, Math.max(height * 2, 1));
237
+ });
238
+ }
239
+ drawCurve(ctx, frequencyData) {
240
+ ctx.strokeStyle = this.color;
241
+ ctx.fillStyle = this.color;
242
+ const canvas = ctx.canvas;
243
+ const waveWidth = canvas.width / devicePixelRatio;
244
+ const waveHeight = canvas.height / devicePixelRatio;
245
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
246
+ ctx.beginPath();
247
+ frequencyData.forEach((value, i) => {
248
+ const x = i / frequencyData.length * waveWidth;
249
+ const y = (1 - value / 255) * waveHeight;
250
+ if (i === 0) {
251
+ ctx.moveTo(x, y);
252
+ } else {
253
+ ctx.lineTo(x, y);
197
254
  }
198
- }
255
+ });
256
+ ctx.lineWidth = 4;
257
+ ctx.stroke();
258
+ }
259
+ drawPixel(ctx, frequencyData) {
260
+ ctx.strokeStyle = this.color;
261
+ ctx.fillStyle = this.color;
262
+ const canvas = ctx.canvas;
263
+ const waveWidth = canvas.width / devicePixelRatio;
264
+ const waveHeight = canvas.height / devicePixelRatio;
265
+ const baseline = waveHeight / 2;
266
+ const barWidth = waveWidth / frequencyData.length * 0.97;
267
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
268
+ frequencyData.forEach((value, i) => {
269
+ const x = i * (waveWidth / frequencyData.length);
270
+ const barHeight = value / 255 * baseline;
271
+ const y = baseline - barHeight;
272
+ ctx.fillRect(x, y, barWidth, barHeight * 2);
273
+ });
274
+ }
275
+ drawWave(ctx, frequencyData) {
276
+ ctx.strokeStyle = this.color;
277
+ ctx.fillStyle = this.color;
278
+ const canvas = ctx.canvas;
279
+ const waveWidth = canvas.width / devicePixelRatio;
280
+ const waveHeight = canvas.height / devicePixelRatio;
281
+ const baseline = waveHeight / 2;
282
+ const barWidth = waveWidth / frequencyData.length * 0.97;
283
+ ctx.clearRect(0, 0, waveWidth, waveHeight);
284
+ ctx.beginPath();
285
+ ctx.moveTo(0, baseline);
286
+ ctx.lineTo(waveWidth, baseline);
287
+ ctx.strokeStyle = this.color;
288
+ ctx.lineWidth = 2;
289
+ ctx.stroke();
290
+ frequencyData.forEach((value, i) => {
291
+ const x = i * (waveWidth / frequencyData.length);
292
+ const barHeight = value / 255 * (waveHeight / 2);
293
+ const y = baseline - barHeight;
294
+ ctx.fillRect(x, y, barWidth, barHeight * 2);
295
+ });
296
+ }
297
+ get durationMs() {
298
+ return this.targetElement.durationMs;
199
299
  }
200
300
  get targetElement() {
201
301
  const target = document.getElementById(this.targetSelector ?? "");
@@ -204,10 +304,17 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
204
304
  }
205
305
  throw new Error("Invalid target, must be an EFAudio or EFVideo element");
206
306
  }
307
+ updated(changedProperties) {
308
+ super.updated(changedProperties);
309
+ if (changedProperties.has("color") || changedProperties.has("mode")) {
310
+ this.frameTask.run();
311
+ }
312
+ }
207
313
  };
208
314
  EFWaveform.styles = [
209
315
  css`
210
- svg {
316
+ :host {
317
+ display: block;
211
318
  all: inherit;
212
319
  }
213
320
  `
@@ -1,12 +1,12 @@
1
1
  import { provide } from "@lit/context";
2
2
  import { state, property } from "lit/decorators.js";
3
+ import { createRef } from "lit/directives/ref.js";
4
+ import { apiHostContext } from "./apiHostContext.js";
5
+ import { efContext } from "./efContext.js";
6
+ import { fetchContext } from "./fetchContext.js";
3
7
  import { focusContext } from "./focusContext.js";
4
8
  import { focusedElementContext } from "./focusedElementContext.js";
5
- import { fetchContext } from "./fetchContext.js";
6
- import { createRef } from "lit/directives/ref.js";
7
9
  import { playingContext, loopContext } from "./playingContext.js";
8
- import { efContext } from "./efContext.js";
9
- import { apiHostContext } from "./apiHostContext.js";
10
10
  var __defProp = Object.defineProperty;
11
11
  var __decorateClass = (decorators, target, key, kind) => {
12
12
  var result = void 0;
@@ -16,8 +16,13 @@ var __decorateClass = (decorators, target, key, kind) => {
16
16
  if (result) __defProp(target, key, result);
17
17
  return result;
18
18
  };
19
+ const contextMixinSymbol = Symbol("contextMixin");
20
+ function isContextMixin(value) {
21
+ return typeof value === "object" && value !== null && contextMixinSymbol in value.constructor;
22
+ }
19
23
  function ContextMixin(superClass) {
20
- class ContextElement extends superClass {
24
+ var _a, _b;
25
+ class ContextElement extends (_b = superClass, _a = contextMixinSymbol, _b) {
21
26
  constructor() {
22
27
  super(...arguments);
23
28
  this.focusContext = this;
@@ -45,6 +50,8 @@ function ContextMixin(superClass) {
45
50
  Object.assign(init.headers, {
46
51
  authorization: `Bearer ${urlToken}`
47
52
  });
53
+ } else {
54
+ init.credentials = "include";
48
55
  }
49
56
  return fetch(url, init);
50
57
  };
@@ -56,6 +63,8 @@ function ContextMixin(superClass) {
56
63
  this.currentTimeMs = 0;
57
64
  this.stageRef = createRef();
58
65
  this.canvasRef = createRef();
66
+ this.#FPS = 30;
67
+ this.#MS_PER_FRAME = 1e3 / this.#FPS;
59
68
  this.setStageScale = () => {
60
69
  if (this.isConnected && !this.rendering) {
61
70
  const canvasElement = this.canvasRef.value;
@@ -95,7 +104,12 @@ function ContextMixin(superClass) {
95
104
  this.#playbackAnimationFrameRequest = null;
96
105
  this.#AUDIO_PLAYBACK_SLICE_MS = 1e3;
97
106
  }
107
+ static {
108
+ this[_a] = true;
109
+ }
98
110
  #URLTokens;
111
+ #FPS;
112
+ #MS_PER_FRAME;
99
113
  connectedCallback() {
100
114
  super.connectedCallback();
101
115
  requestAnimationFrame(this.setStageScale);
@@ -118,6 +132,16 @@ function ContextMixin(superClass) {
118
132
  if (changedProperties.has("currentTimeMs") && this.targetTimegroup) {
119
133
  if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
120
134
  this.targetTimegroup.currentTimeMs = this.currentTimeMs;
135
+ if (this.isConnected) {
136
+ this.dispatchEvent(
137
+ new CustomEvent("timeupdate", {
138
+ detail: {
139
+ currentTimeMs: this.currentTimeMs,
140
+ progress: this.currentTimeMs / this.targetTimegroup.durationMs
141
+ }
142
+ })
143
+ );
144
+ }
121
145
  }
122
146
  }
123
147
  super.update(changedProperties);
@@ -135,7 +159,11 @@ function ContextMixin(superClass) {
135
159
  #playbackAnimationFrameRequest;
136
160
  #AUDIO_PLAYBACK_SLICE_MS;
137
161
  #syncPlayheadToAudioContext(target, startMs) {
138
- this.currentTimeMs = startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1e3;
162
+ const rawTimeMs = startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1e3;
163
+ const nextTimeMs = Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;
164
+ if (nextTimeMs !== this.currentTimeMs) {
165
+ this.currentTimeMs = nextTimeMs;
166
+ }
139
167
  this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
140
168
  this.#syncPlayheadToAudioContext(target, startMs);
141
169
  });
@@ -262,5 +290,6 @@ function ContextMixin(superClass) {
262
290
  return ContextElement;
263
291
  }
264
292
  export {
265
- ContextMixin
293
+ ContextMixin,
294
+ isContextMixin
266
295
  };