@editframe/elements 0.11.0-beta.9 → 0.12.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.d.ts +8 -15
- package/dist/assets/src/MP4File.js +73 -20
- package/dist/elements/EFCaptions.d.ts +50 -6
- package/dist/elements/EFMedia.d.ts +1 -2
- package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
- package/dist/elements/EFTimegroup.d.ts +23 -2
- package/dist/elements/EFWaveform.d.ts +15 -11
- package/dist/elements/src/EF_FRAMEGEN.js +24 -26
- package/dist/elements/src/elements/EFCaptions.js +295 -42
- package/dist/elements/src/elements/EFImage.js +0 -6
- package/dist/elements/src/elements/EFMedia.js +70 -18
- package/dist/elements/src/elements/EFTemporal.js +13 -10
- package/dist/elements/src/elements/EFTimegroup.js +37 -12
- package/dist/elements/src/elements/EFVideo.js +1 -4
- package/dist/elements/src/elements/EFWaveform.js +250 -143
- package/dist/elements/src/gui/ContextMixin.js +44 -11
- package/dist/elements/src/gui/EFPreview.js +3 -1
- package/dist/elements/src/gui/EFScrubber.js +142 -0
- package/dist/elements/src/gui/EFTimeDisplay.js +81 -0
- package/dist/elements/src/gui/EFTogglePlay.js +11 -19
- package/dist/elements/src/gui/EFWorkbench.js +1 -24
- package/dist/elements/src/gui/TWMixin.css.js +1 -1
- package/dist/elements/src/index.js +8 -1
- package/dist/gui/ContextMixin.d.ts +2 -1
- package/dist/gui/EFScrubber.d.ts +23 -0
- package/dist/gui/EFTimeDisplay.d.ts +17 -0
- package/dist/gui/EFTogglePlay.d.ts +0 -2
- package/dist/gui/EFWorkbench.d.ts +0 -1
- package/dist/index.d.ts +3 -1
- package/dist/style.css +6 -801
- package/package.json +2 -2
- package/src/elements/EFCaptions.browsertest.ts +6 -6
- package/src/elements/EFCaptions.ts +325 -56
- package/src/elements/EFImage.browsertest.ts +4 -17
- package/src/elements/EFImage.ts +0 -6
- package/src/elements/EFMedia.browsertest.ts +10 -19
- package/src/elements/EFMedia.ts +87 -20
- package/src/elements/EFTemporal.browsertest.ts +14 -0
- package/src/elements/EFTemporal.ts +14 -0
- package/src/elements/EFTimegroup.browsertest.ts +37 -0
- package/src/elements/EFTimegroup.ts +42 -17
- package/src/elements/EFVideo.ts +1 -4
- package/src/elements/EFWaveform.ts +339 -314
- package/src/gui/ContextMixin.browsertest.ts +28 -2
- package/src/gui/ContextMixin.ts +52 -14
- package/src/gui/EFPreview.ts +4 -2
- package/src/gui/EFScrubber.ts +145 -0
- package/src/gui/EFTimeDisplay.ts +81 -0
- package/src/gui/EFTogglePlay.ts +19 -25
- package/src/gui/EFWorkbench.ts +3 -36
- package/dist/elements/src/elements/util.js +0 -11
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { html, css, LitElement } from "lit";
|
|
2
1
|
import { provide } from "@lit/context";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
|
-
import { property, customElement } from "lit/decorators.js";
|
|
5
3
|
import debug from "debug";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { html, css, LitElement } from "lit";
|
|
5
|
+
import { property, customElement } from "lit/decorators.js";
|
|
8
6
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
7
|
+
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
9
8
|
import { deepGetMediaElements } from "./EFMedia.js";
|
|
9
|
+
import { EFTemporal, shallowGetTemporalElements, isEFTemporal, timegroupContext } from "./EFTemporal.js";
|
|
10
|
+
import { TimegroupController } from "./TimegroupController.js";
|
|
10
11
|
import { durationConverter } from "./durationConverter.js";
|
|
11
12
|
var __defProp = Object.defineProperty;
|
|
12
13
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -138,10 +139,16 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
|
|
|
138
139
|
throw new Error(`Invalid time mode: ${this.mode}`);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Wait for all media elements to load their initial segments.
|
|
144
|
+
* Ideally we would only need the extracted index json data, but
|
|
145
|
+
* that caused issues with constructing audio data. We had negative durations
|
|
146
|
+
* in calculations and it was not clear why.
|
|
147
|
+
*/
|
|
141
148
|
async waitForMediaDurations() {
|
|
142
149
|
return await Promise.all(
|
|
143
150
|
deepGetMediaElements(this).map(
|
|
144
|
-
(media) => media.
|
|
151
|
+
(media) => media.initSegmentsLoader.taskComplete
|
|
145
152
|
)
|
|
146
153
|
);
|
|
147
154
|
}
|
|
@@ -212,8 +219,28 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
|
|
|
212
219
|
}
|
|
213
220
|
}
|
|
214
221
|
}
|
|
222
|
+
get contextProvider() {
|
|
223
|
+
let parent = this.parentNode;
|
|
224
|
+
while (parent) {
|
|
225
|
+
if (isContextMixin(parent)) {
|
|
226
|
+
return parent;
|
|
227
|
+
}
|
|
228
|
+
parent = parent.parentNode;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns true if the timegroup should be wrapped with a workbench.
|
|
234
|
+
*
|
|
235
|
+
* A timegroup should be wrapped with a workbench if it is the root-most timegroup
|
|
236
|
+
* and EF_INTERACTIVE is true.
|
|
237
|
+
*
|
|
238
|
+
* If the timegroup is already wrappedin a context provider like ef-preview,
|
|
239
|
+
* it should NOT be wrapped in a workbench.
|
|
240
|
+
*
|
|
241
|
+
*/
|
|
215
242
|
shouldWrapWithWorkbench() {
|
|
216
|
-
return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-
|
|
243
|
+
return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
|
|
217
244
|
}
|
|
218
245
|
wrapWithWorkbench() {
|
|
219
246
|
const workbench = document.createElement("ef-workbench");
|
|
@@ -270,10 +297,8 @@ _currentTime = /* @__PURE__ */ new WeakMap();
|
|
|
270
297
|
_EFTimegroup_instances = /* @__PURE__ */ new WeakSet();
|
|
271
298
|
addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
|
|
272
299
|
await this.waitForMediaDurations();
|
|
273
|
-
const durationMs = toMs - fromMs;
|
|
274
300
|
await Promise.all(
|
|
275
301
|
deepGetMediaElements(this).map(async (mediaElement) => {
|
|
276
|
-
await mediaElement.trackFragmentIndexLoader.taskComplete;
|
|
277
302
|
const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
|
|
278
303
|
const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
|
|
279
304
|
const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
|
|
@@ -284,15 +309,15 @@ addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
|
|
|
284
309
|
if (!audio) {
|
|
285
310
|
throw new Error("Failed to fetch audio");
|
|
286
311
|
}
|
|
287
|
-
const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
|
|
288
|
-
const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
|
|
289
|
-
const ctxDurationMs = ctxEndMs - ctxStartMs;
|
|
290
|
-
const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
|
|
291
312
|
const bufferSource = audioContext.createBufferSource();
|
|
292
313
|
bufferSource.buffer = await audioContext.decodeAudioData(
|
|
293
314
|
await audio.blob.arrayBuffer()
|
|
294
315
|
);
|
|
295
316
|
bufferSource.connect(audioContext.destination);
|
|
317
|
+
const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
|
|
318
|
+
const ctxEndMs = mediaElement.endTimeMs - fromMs;
|
|
319
|
+
const ctxDurationMs = ctxEndMs - ctxStartMs;
|
|
320
|
+
const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
|
|
296
321
|
bufferSource.start(
|
|
297
322
|
ctxStartMs / 1e3,
|
|
298
323
|
offset / 1e3,
|
|
@@ -85,10 +85,7 @@ let EFVideo = class extends TWMixin(EFMedia) {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
render() {
|
|
88
|
-
return html` <canvas
|
|
89
|
-
class="h-full w-full object-fill"
|
|
90
|
-
${ref(this.canvasRef)}
|
|
91
|
-
></canvas>`;
|
|
88
|
+
return html` <canvas ${ref(this.canvasRef)}></canvas>`;
|
|
92
89
|
}
|
|
93
90
|
get canvasElement() {
|
|
94
91
|
return this.canvasRef.value;
|
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
316
|
+
:host {
|
|
317
|
+
display: block;
|
|
211
318
|
all: inherit;
|
|
212
319
|
}
|
|
213
320
|
`
|