@editframe/elements 0.11.0-beta.8 → 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.
- package/dist/EF_FRAMEGEN.d.ts +8 -15
- package/dist/elements/EFCaptions.d.ts +50 -6
- package/dist/elements/EFMedia.d.ts +1 -1
- package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
- package/dist/elements/EFTimegroup.d.ts +23 -2
- package/dist/elements/EFWaveform.d.ts +17 -13
- 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 +3 -13
- package/dist/elements/src/elements/EFMedia.js +0 -5
- 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 +7 -7
- package/dist/elements/src/elements/EFWaveform.js +262 -149
- package/dist/elements/src/gui/ContextMixin.js +36 -7
- package/dist/elements/src/gui/EFFilmstrip.js +16 -3
- 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 +14 -14
- 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 +1 -1
- 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 +4 -14
- package/src/elements/EFMedia.browsertest.ts +8 -19
- package/src/elements/EFMedia.ts +1 -6
- 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 +7 -8
- package/src/elements/EFWaveform.ts +349 -319
- package/src/gui/ContextMixin.browsertest.ts +28 -2
- package/src/gui/ContextMixin.ts +41 -9
- package/src/gui/EFFilmstrip.ts +16 -3
- package/src/gui/EFScrubber.ts +145 -0
- package/src/gui/EFTimeDisplay.ts +81 -0
- package/src/gui/EFTogglePlay.ts +21 -21
- package/src/gui/EFWorkbench.ts +3 -36
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { EFAudio } from "./EFAudio.js";
|
|
2
|
-
import { html, LitElement } from "lit";
|
|
3
|
-
import { property, customElement } from "lit/decorators.js";
|
|
4
|
-
import { EFVideo } from "./EFVideo.js";
|
|
5
|
-
import { EFTemporal } from "./EFTemporal.js";
|
|
6
|
-
import { CrossUpdateController } from "./CrossUpdateController.js";
|
|
7
|
-
import { TWMixin } from "../gui/TWMixin.js";
|
|
8
2
|
import { Task } from "@lit/task";
|
|
9
|
-
import
|
|
3
|
+
import { html, css, LitElement } from "lit";
|
|
4
|
+
import { property, customElement } from "lit/decorators.js";
|
|
10
5
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
11
6
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
7
|
+
import { TWMixin } from "../gui/TWMixin.js";
|
|
8
|
+
import { CrossUpdateController } from "./CrossUpdateController.js";
|
|
9
|
+
import { EFTemporal } from "./EFTemporal.js";
|
|
10
|
+
import { EFVideo } from "./EFVideo.js";
|
|
12
11
|
var __defProp = Object.defineProperty;
|
|
13
12
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
13
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -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,8 +304,21 @@ 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
|
-
EFWaveform.styles = [
|
|
314
|
+
EFWaveform.styles = [
|
|
315
|
+
css`
|
|
316
|
+
:host {
|
|
317
|
+
display: block;
|
|
318
|
+
all: inherit;
|
|
319
|
+
}
|
|
320
|
+
`
|
|
321
|
+
];
|
|
209
322
|
__decorateClass([
|
|
210
323
|
property({
|
|
211
324
|
type: String,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|
|
@@ -74,16 +74,29 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
74
74
|
return this.element && this.focusContext?.focusedElement === this.element;
|
|
75
75
|
}
|
|
76
76
|
get gutterStyles() {
|
|
77
|
+
if (this.element.sourceInMs || this.element.sourceOutMs) {
|
|
78
|
+
return {
|
|
79
|
+
position: "relative",
|
|
80
|
+
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs - this.element.sourceInMs)}px`,
|
|
81
|
+
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs + this.element.sourceOutMs + this.element.sourceInMs)}px`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
77
84
|
return {
|
|
78
85
|
position: "relative",
|
|
79
|
-
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs
|
|
80
|
-
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs
|
|
86
|
+
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs)}px`,
|
|
87
|
+
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs)}px`
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
get trimPortionStyles() {
|
|
91
|
+
if (this.element.sourceInMs || this.element.sourceOutMs) {
|
|
92
|
+
return {
|
|
93
|
+
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
94
|
+
left: `${this.pixelsPerMs * (this.element.trimStartMs + this.element.sourceInMs)}px`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
84
97
|
return {
|
|
85
98
|
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
86
|
-
left: `${this.pixelsPerMs *
|
|
99
|
+
left: `${this.pixelsPerMs * this.element.trimStartMs}px`
|
|
87
100
|
};
|
|
88
101
|
}
|
|
89
102
|
render() {
|