@editframe/elements 0.55.2 → 0.56.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 (41) hide show
  1. package/dist/elements/EFTemporal.d.ts +2 -0
  2. package/dist/elements/EFTemporal.js +12 -0
  3. package/dist/elements/EFTemporal.js.map +1 -1
  4. package/dist/elements/EFVideo.js.map +1 -1
  5. package/dist/gui/ContextMixin.d.ts +2 -0
  6. package/dist/gui/ContextMixin.js +44 -0
  7. package/dist/gui/ContextMixin.js.map +1 -1
  8. package/dist/gui/Controllable.d.ts +2 -0
  9. package/dist/gui/Controllable.js +6 -0
  10. package/dist/gui/Controllable.js.map +1 -1
  11. package/dist/gui/EFControls.d.ts +2 -0
  12. package/dist/gui/EFControls.js +14 -1
  13. package/dist/gui/EFControls.js.map +1 -1
  14. package/dist/gui/EFFullscreen.d.ts +38 -0
  15. package/dist/gui/EFFullscreen.js +50 -0
  16. package/dist/gui/EFFullscreen.js.map +1 -0
  17. package/dist/gui/EFMute.d.ts +40 -0
  18. package/dist/gui/EFMute.js +41 -0
  19. package/dist/gui/EFMute.js.map +1 -0
  20. package/dist/gui/EFPIP.d.ts +43 -0
  21. package/dist/gui/EFPIP.js +89 -0
  22. package/dist/gui/EFPIP.js.map +1 -0
  23. package/dist/gui/EFResolution.d.ts +47 -0
  24. package/dist/gui/EFResolution.js +203 -0
  25. package/dist/gui/EFResolution.js.map +1 -0
  26. package/dist/gui/EFVolume.d.ts +44 -0
  27. package/dist/gui/EFVolume.js +125 -0
  28. package/dist/gui/EFVolume.js.map +1 -0
  29. package/dist/gui/PlaybackController.d.ts +5 -1
  30. package/dist/gui/PlaybackController.js +50 -1
  31. package/dist/gui/PlaybackController.js.map +1 -1
  32. package/dist/gui/icons.js +5 -1
  33. package/dist/gui/icons.js.map +1 -1
  34. package/dist/gui/volumeContext.d.ts +10 -0
  35. package/dist/gui/volumeContext.js +8 -0
  36. package/dist/gui/volumeContext.js.map +1 -0
  37. package/dist/index.d.ts +7 -1
  38. package/dist/index.js +7 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +3 -3
@@ -0,0 +1,43 @@
1
+ import { TemplateResult as TemplateResult$1 } from "../node_modules/lit-html/development/lit-html.js";
2
+ import * as _$lit from "lit";
3
+ import { LitElement } from "lit";
4
+
5
+ //#region src/gui/EFPIP.d.ts
6
+ /**
7
+ * A Picture-in-Picture toggle.
8
+ *
9
+ * The `target` attribute accepts the `id` of either:
10
+ * - A native `<video>` element — PiP is requested directly.
11
+ * - A `<canvas>` element — a hidden `<video>` is created via `captureStream()` and used for PiP.
12
+ * - A container element — the first `<video>` or `<canvas>` descendant is used.
13
+ *
14
+ * Usage:
15
+ * ```html
16
+ * <ef-pip target="my-canvas">
17
+ * <button slot="enter">PiP</button>
18
+ * <button slot="exit">Exit PiP</button>
19
+ * </ef-pip>
20
+ * ```
21
+ */
22
+ declare class EFPIP extends LitElement {
23
+ #private;
24
+ static styles: _$lit.CSSResult[];
25
+ /** ID of a <video>, <canvas>, or container element to use for PiP. */
26
+ target: string;
27
+ private isPiP;
28
+ private resolveTargetVideo;
29
+ private handleEnterPiP;
30
+ private handleLeavePiP;
31
+ private toggle;
32
+ connectedCallback(): void;
33
+ disconnectedCallback(): void;
34
+ render(): TemplateResult$1<1>;
35
+ }
36
+ declare global {
37
+ interface HTMLElementTagNameMap {
38
+ "ef-pip": EFPIP;
39
+ }
40
+ }
41
+ //#endregion
42
+ export { EFPIP };
43
+ //# sourceMappingURL=EFPIP.d.ts.map
@@ -0,0 +1,89 @@
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.122.0/helpers/decorate.js";
2
+ import { LitElement, css, html } from "lit";
3
+ import { customElement, property, state } from "lit/decorators.js";
4
+ //#region src/gui/EFPIP.ts
5
+ let EFPIP = class EFPIP extends LitElement {
6
+ constructor(..._args) {
7
+ super(..._args);
8
+ this.target = "";
9
+ this.isPiP = false;
10
+ this.handleEnterPiP = () => {
11
+ this.isPiP = true;
12
+ };
13
+ this.handleLeavePiP = () => {
14
+ this.isPiP = false;
15
+ };
16
+ this.toggle = async () => {
17
+ if (document.pictureInPictureElement) {
18
+ await document.exitPictureInPicture();
19
+ return;
20
+ }
21
+ const video = this.resolveTargetVideo();
22
+ if (!video) return;
23
+ video.addEventListener("enterpictureinpicture", this.handleEnterPiP, { once: false });
24
+ video.addEventListener("leavepictureinpicture", this.handleLeavePiP, { once: false });
25
+ if (video.paused) await video.play().catch(() => {});
26
+ await video.requestPictureInPicture();
27
+ };
28
+ }
29
+ static {
30
+ this.styles = [css`
31
+ :host {}
32
+ `];
33
+ }
34
+ /** Hidden video element created from a canvas capture stream. */
35
+ #captureVideo = null;
36
+ resolveTargetVideo() {
37
+ const root = this.target ? document.getElementById(this.target) : null;
38
+ if (!root) return null;
39
+ if (root instanceof HTMLVideoElement) return root;
40
+ if (root instanceof HTMLCanvasElement) return this.#getOrCreateCaptureVideo(root);
41
+ const video = root.querySelector("video");
42
+ if (video) return video;
43
+ const canvas = root.querySelector("canvas");
44
+ if (canvas) return this.#getOrCreateCaptureVideo(canvas);
45
+ return null;
46
+ }
47
+ #getOrCreateCaptureVideo(canvas) {
48
+ if (!this.#captureVideo) {
49
+ const video = document.createElement("video");
50
+ video.style.cssText = "position:absolute;width:1px;height:1px;opacity:0;pointer-events:none";
51
+ video.muted = true;
52
+ video.autoplay = true;
53
+ const stream = canvas.captureStream?.();
54
+ if (stream) video.srcObject = stream;
55
+ document.body.appendChild(video);
56
+ this.#captureVideo = video;
57
+ }
58
+ return this.#captureVideo;
59
+ }
60
+ #teardownCaptureVideo() {
61
+ if (this.#captureVideo) {
62
+ this.#captureVideo.srcObject = null;
63
+ this.#captureVideo.remove();
64
+ this.#captureVideo = null;
65
+ }
66
+ }
67
+ connectedCallback() {
68
+ super.connectedCallback();
69
+ this.isPiP = !!document.pictureInPictureElement;
70
+ }
71
+ disconnectedCallback() {
72
+ super.disconnectedCallback();
73
+ this.#teardownCaptureVideo();
74
+ }
75
+ render() {
76
+ return html`
77
+ <div @click=${this.toggle}>
78
+ ${this.isPiP ? html`<slot name="exit"></slot>` : html`<slot name="enter"></slot>`}
79
+ </div>
80
+ `;
81
+ }
82
+ };
83
+ __decorate([property({ type: String })], EFPIP.prototype, "target", void 0);
84
+ __decorate([state()], EFPIP.prototype, "isPiP", void 0);
85
+ EFPIP = __decorate([customElement("ef-pip")], EFPIP);
86
+ //#endregion
87
+ export { EFPIP };
88
+
89
+ //# sourceMappingURL=EFPIP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EFPIP.js","names":["#getOrCreateCaptureVideo","#captureVideo","#teardownCaptureVideo"],"sources":["../../src/gui/EFPIP.ts"],"mappings":";;;;AAoBO,IAAA,QAAA,MAAM,cAAc,WAAW;;;gBAS3B;eAGO;8BAmDe;AAC7B,QAAK,QAAQ;;8BAGgB;AAC7B,QAAK,QAAQ;;gBAGE,YAAY;AAC3B,OAAI,SAAS,yBAAyB;AACpC,UAAM,SAAS,sBAAsB;AACrC;;GAGF,MAAM,QAAQ,KAAK,oBAAoB;AACvC,OAAI,CAAC,MAAO;AAEZ,SAAM,iBAAiB,yBAAyB,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACrF,SAAM,iBAAiB,yBAAyB,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAGrF,OAAI,MAAM,OACR,OAAM,MAAM,MAAM,CAAC,YAAY,GAAG;AAGpC,SAAM,MAAM,yBAAyB;;;;gBAvFvB,CACd,GAAG;;MAGJ;;;CAUD,gBAAyC;CAEzC,qBAAsD;EACpD,MAAM,OAAO,KAAK,SAAS,SAAS,eAAe,KAAK,OAAO,GAAG;AAClE,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,gBAAgB,iBAClB,QAAO;AAGT,MAAI,gBAAgB,kBAClB,QAAO,MAAA,wBAA8B,KAAK;EAI5C,MAAM,QAAQ,KAAK,cAAc,QAAQ;AACzC,MAAI,MAAO,QAAO;EAClB,MAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,MAAI,OAAQ,QAAO,MAAA,wBAA8B,OAAO;AAExD,SAAO;;CAGT,yBAAyB,QAA6C;AACpE,MAAI,CAAC,MAAA,cAAoB;GACvB,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,SAAM,MAAM,UAAU;AACtB,SAAM,QAAQ;AACd,SAAM,WAAW;GAEjB,MAAM,SAAU,OAAe,iBAAiB;AAChD,OAAI,OACF,OAAM,YAAY;AAEpB,YAAS,KAAK,YAAY,MAAM;AAChC,SAAA,eAAqB;;AAEvB,SAAO,MAAA;;CAGT,wBAAwB;AACtB,MAAI,MAAA,cAAoB;AACtB,SAAA,aAAmB,YAAY;AAC/B,SAAA,aAAmB,QAAQ;AAC3B,SAAA,eAAqB;;;CAgCzB,oBAAoB;AAClB,QAAM,mBAAmB;AACzB,OAAK,QAAQ,CAAC,CAAC,SAAS;;CAG1B,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,QAAA,sBAA4B;;CAG9B,SAAS;AACP,SAAO,IAAI;oBACK,KAAK,OAAO;UACtB,KAAK,QAAQ,IAAI,8BAA8B,IAAI,6BAA6B;;;;;YAhGvF,SAAS,EAAE,MAAM,QAAQ,CAAC,CAAA,EAAA,MAAA,WAAA,UAAA,KAAA,EAAA;YAG1B,OAAO,CAAA,EAAA,MAAA,WAAA,SAAA,KAAA,EAAA;oBAZT,cAAc,SAAS,CAAA,EAAA,MAAA"}
@@ -0,0 +1,47 @@
1
+ import { TemplateResult as TemplateResult$1 } from "../node_modules/lit-html/development/lit-html.js";
2
+ import * as _$lit from "lit";
3
+ import { LitElement } from "lit";
4
+
5
+ //#region src/gui/EFResolution.d.ts
6
+ interface ResolutionChangeDetail {
7
+ width: number;
8
+ height: number;
9
+ }
10
+ /**
11
+ * A resolution picker for an `ef-timegroup` (or any element whose dimensions
12
+ * are controlled by inline CSS `width` / `height`).
13
+ *
14
+ * The `target` attribute accepts the `id` of the element to resize.
15
+ * Dispatches a `resolution-change` custom event with `{ width, height }` detail.
16
+ *
17
+ * Usage:
18
+ * ```html
19
+ * <ef-resolution target="my-timegroup"></ef-resolution>
20
+ * ```
21
+ */
22
+ declare class EFResolution extends LitElement {
23
+ #private;
24
+ static styles: _$lit.CSSResult[];
25
+ /** ID of the target element whose CSS width/height to change. */
26
+ target: string;
27
+ private selectedPreset;
28
+ private customWidth;
29
+ private customHeight;
30
+ private isCustom;
31
+ private get targetElement();
32
+ connectedCallback(): void;
33
+ private handlePresetChange;
34
+ private handleCustomWidth;
35
+ private handleCustomHeight;
36
+ private handleCustomApply;
37
+ private handleCustomKeydown;
38
+ render(): TemplateResult$1<1>;
39
+ }
40
+ declare global {
41
+ interface HTMLElementTagNameMap {
42
+ "ef-resolution": EFResolution;
43
+ }
44
+ }
45
+ //#endregion
46
+ export { EFResolution, ResolutionChangeDetail };
47
+ //# sourceMappingURL=EFResolution.d.ts.map
@@ -0,0 +1,203 @@
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.122.0/helpers/decorate.js";
2
+ import { LitElement, css, html } from "lit";
3
+ import { customElement, property, state } from "lit/decorators.js";
4
+ //#region src/gui/EFResolution.ts
5
+ const PRESETS = [
6
+ {
7
+ label: "HD (1920×1080)",
8
+ width: 1920,
9
+ height: 1080
10
+ },
11
+ {
12
+ label: "720p (1280×720)",
13
+ width: 1280,
14
+ height: 720
15
+ },
16
+ {
17
+ label: "4K (3840×2160)",
18
+ width: 3840,
19
+ height: 2160
20
+ },
21
+ {
22
+ label: "Vertical (1080×1920)",
23
+ width: 1080,
24
+ height: 1920
25
+ },
26
+ {
27
+ label: "Square (1080×1080)",
28
+ width: 1080,
29
+ height: 1080
30
+ }
31
+ ];
32
+ const CUSTOM_VALUE = "__custom__";
33
+ let EFResolution = class EFResolution extends LitElement {
34
+ constructor(..._args) {
35
+ super(..._args);
36
+ this.target = "";
37
+ this.selectedPreset = PRESETS[0].label;
38
+ this.customWidth = 1920;
39
+ this.customHeight = 1080;
40
+ this.isCustom = false;
41
+ this.handlePresetChange = (e) => {
42
+ const value = e.target.value;
43
+ if (value === CUSTOM_VALUE) {
44
+ this.selectedPreset = CUSTOM_VALUE;
45
+ this.isCustom = true;
46
+ return;
47
+ }
48
+ const preset = PRESETS.find((p) => p.label === value);
49
+ if (!preset) return;
50
+ this.selectedPreset = preset.label;
51
+ this.isCustom = false;
52
+ this.#applyDimensions(preset.width, preset.height);
53
+ };
54
+ this.handleCustomWidth = (e) => {
55
+ const input = e.target;
56
+ this.customWidth = Number.parseInt(input.value, 10) || this.customWidth;
57
+ };
58
+ this.handleCustomHeight = (e) => {
59
+ const input = e.target;
60
+ this.customHeight = Number.parseInt(input.value, 10) || this.customHeight;
61
+ };
62
+ this.handleCustomApply = () => {
63
+ if (this.customWidth > 0 && this.customHeight > 0) this.#applyDimensions(this.customWidth, this.customHeight);
64
+ };
65
+ this.handleCustomKeydown = (e) => {
66
+ if (e.key === "Enter") this.handleCustomApply();
67
+ };
68
+ }
69
+ static {
70
+ this.styles = [css`
71
+ :host {
72
+ --ef-resolution-font-size: 12px;
73
+ --ef-resolution-color: var(--ef-color-text, #fff);
74
+ --ef-resolution-border-color: var(--ef-color-border, rgba(255, 255, 255, 0.2));
75
+ --ef-resolution-bg: var(--ef-color-surface, rgba(0, 0, 0, 0.6));
76
+ display: inline-flex;
77
+ align-items: center;
78
+ gap: 6px;
79
+ font-size: var(--ef-resolution-font-size);
80
+ color: var(--ef-resolution-color);
81
+ }
82
+
83
+ select,
84
+ input[type="number"] {
85
+ background: var(--ef-resolution-bg);
86
+ border: 1px solid var(--ef-resolution-border-color);
87
+ border-radius: 3px;
88
+ color: inherit;
89
+ font-size: inherit;
90
+ padding: 2px 4px;
91
+ outline: none;
92
+ cursor: pointer;
93
+ }
94
+
95
+ select {
96
+ min-width: 130px;
97
+ }
98
+
99
+ input[type="number"] {
100
+ width: 60px;
101
+ cursor: text;
102
+ -moz-appearance: textfield;
103
+ }
104
+
105
+ input[type="number"]::-webkit-inner-spin-button,
106
+ input[type="number"]::-webkit-outer-spin-button {
107
+ -webkit-appearance: none;
108
+ margin: 0;
109
+ }
110
+
111
+ .custom-inputs {
112
+ display: contents;
113
+ }
114
+
115
+ .sep {
116
+ opacity: 0.5;
117
+ }
118
+ `];
119
+ }
120
+ get targetElement() {
121
+ return this.target ? document.getElementById(this.target) : null;
122
+ }
123
+ connectedCallback() {
124
+ super.connectedCallback();
125
+ const el = this.targetElement;
126
+ if (el) {
127
+ const w = el.offsetWidth || 1920;
128
+ const h = el.offsetHeight || 1080;
129
+ this.#syncFromDimensions(w, h);
130
+ }
131
+ }
132
+ #syncFromDimensions(width, height) {
133
+ const match = PRESETS.find((p) => p.width === width && p.height === height);
134
+ if (match) {
135
+ this.selectedPreset = match.label;
136
+ this.isCustom = false;
137
+ } else {
138
+ this.customWidth = width;
139
+ this.customHeight = height;
140
+ this.selectedPreset = CUSTOM_VALUE;
141
+ this.isCustom = true;
142
+ }
143
+ }
144
+ #applyDimensions(width, height) {
145
+ const el = this.targetElement;
146
+ if (el) {
147
+ el.style.width = `${width}px`;
148
+ el.style.height = `${height}px`;
149
+ }
150
+ this.dispatchEvent(new CustomEvent("resolution-change", {
151
+ detail: {
152
+ width,
153
+ height
154
+ },
155
+ bubbles: true,
156
+ composed: true
157
+ }));
158
+ }
159
+ render() {
160
+ return html`
161
+ <select .value=${this.selectedPreset} @change=${this.handlePresetChange}>
162
+ ${PRESETS.map((p) => html`<option value=${p.label} ?selected=${this.selectedPreset === p.label}>
163
+ ${p.label}
164
+ </option>`)}
165
+ <option value=${CUSTOM_VALUE} ?selected=${this.isCustom}>Custom</option>
166
+ </select>
167
+
168
+ ${this.isCustom ? html`
169
+ <span class="custom-inputs">
170
+ <input
171
+ type="number"
172
+ min="1"
173
+ max="7680"
174
+ .value=${String(this.customWidth)}
175
+ @change=${this.handleCustomWidth}
176
+ @keydown=${this.handleCustomKeydown}
177
+ aria-label="Width"
178
+ />
179
+ <span class="sep">×</span>
180
+ <input
181
+ type="number"
182
+ min="1"
183
+ max="7680"
184
+ .value=${String(this.customHeight)}
185
+ @change=${this.handleCustomHeight}
186
+ @keydown=${this.handleCustomKeydown}
187
+ aria-label="Height"
188
+ />
189
+ </span>
190
+ ` : html``}
191
+ `;
192
+ }
193
+ };
194
+ __decorate([property({ type: String })], EFResolution.prototype, "target", void 0);
195
+ __decorate([state()], EFResolution.prototype, "selectedPreset", void 0);
196
+ __decorate([state()], EFResolution.prototype, "customWidth", void 0);
197
+ __decorate([state()], EFResolution.prototype, "customHeight", void 0);
198
+ __decorate([state()], EFResolution.prototype, "isCustom", void 0);
199
+ EFResolution = __decorate([customElement("ef-resolution")], EFResolution);
200
+ //#endregion
201
+ export { EFResolution };
202
+
203
+ //# sourceMappingURL=EFResolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EFResolution.js","names":["#applyDimensions","#syncFromDimensions"],"sources":["../../src/gui/EFResolution.ts"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { TemplateResult as TemplateResult$1 } from "../node_modules/lit-html/development/lit-html.js";
2
+ import { ControllableInterface } from "./Controllable.js";
3
+ import * as _$lit from "lit";
4
+ import { LitElement } from "lit";
5
+
6
+ //#region src/gui/EFVolume.d.ts
7
+ declare const EFVolume_base: (new (...args: any[]) => {
8
+ target: string;
9
+ targetElement: ControllableInterface | null;
10
+ effectiveContext: ControllableInterface | null;
11
+ }) & typeof LitElement;
12
+ /**
13
+ * A range slider for controlling master playback volume (0–1).
14
+ * Must be a descendant of `ef-controls` or `ef-preview`, or have a `target` attribute.
15
+ *
16
+ * Usage:
17
+ * ```html
18
+ * <ef-controls target="my-preview">
19
+ * <ef-volume></ef-volume>
20
+ * </ef-controls>
21
+ * ```
22
+ *
23
+ * CSS custom properties:
24
+ * - `--ef-volume-height`: track height (default 4px)
25
+ * - `--ef-volume-track-color`: unfilled track background
26
+ * - `--ef-volume-fill-color`: filled track and thumb color
27
+ */
28
+ declare class EFVolume extends EFVolume_base {
29
+ static styles: _$lit.CSSResult[];
30
+ private contextVolume;
31
+ private contextMuted;
32
+ get context(): ControllableInterface | null;
33
+ private get displayVolume();
34
+ private handleInput;
35
+ render(): TemplateResult$1<1>;
36
+ }
37
+ declare global {
38
+ interface HTMLElementTagNameMap {
39
+ "ef-volume": EFVolume;
40
+ }
41
+ }
42
+ //#endregion
43
+ export { EFVolume };
44
+ //# sourceMappingURL=EFVolume.d.ts.map
@@ -0,0 +1,125 @@
1
+ import { efContext } from "./efContext.js";
2
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.122.0/helpers/decorate.js";
3
+ import { mutedContext, volumeContext } from "./volumeContext.js";
4
+ import { TargetOrContextMixin } from "./TargetOrContextMixin.js";
5
+ import { consume } from "@lit/context";
6
+ import { LitElement, css, html } from "lit";
7
+ import { customElement, state } from "lit/decorators.js";
8
+ //#region src/gui/EFVolume.ts
9
+ let EFVolume = class EFVolume extends TargetOrContextMixin(LitElement, efContext) {
10
+ constructor(..._args) {
11
+ super(..._args);
12
+ this.contextVolume = 1;
13
+ this.contextMuted = false;
14
+ this.handleInput = (e) => {
15
+ const input = e.target;
16
+ const value = Number.parseFloat(input.value);
17
+ if (this.context) {
18
+ if (this.context.muted && value > 0) this.context.muted = false;
19
+ this.context.volume = value;
20
+ }
21
+ };
22
+ }
23
+ static {
24
+ this.styles = [css`
25
+ :host {
26
+ --ef-volume-height: 4px;
27
+ --ef-volume-track-color: var(--ef-color-border, rgba(255, 255, 255, 0.2));
28
+ --ef-volume-fill-color: var(--ef-color-primary, #fff);
29
+ display: flex;
30
+ align-items: center;
31
+ width: 80px;
32
+ }
33
+
34
+ input[type="range"] {
35
+ -webkit-appearance: none;
36
+ appearance: none;
37
+ width: 100%;
38
+ height: var(--ef-volume-height);
39
+ background: var(--ef-volume-track-color);
40
+ border-radius: 2px;
41
+ outline: none;
42
+ cursor: pointer;
43
+ padding: 0;
44
+ margin: 0;
45
+ }
46
+
47
+ input[type="range"]::-webkit-slider-thumb {
48
+ -webkit-appearance: none;
49
+ appearance: none;
50
+ width: 12px;
51
+ height: 12px;
52
+ border-radius: 50%;
53
+ background: var(--ef-volume-fill-color);
54
+ cursor: grab;
55
+ }
56
+
57
+ input[type="range"]::-moz-range-thumb {
58
+ width: 12px;
59
+ height: 12px;
60
+ border: none;
61
+ border-radius: 50%;
62
+ background: var(--ef-volume-fill-color);
63
+ cursor: grab;
64
+ }
65
+
66
+ input[type="range"]::-webkit-slider-runnable-track {
67
+ background: linear-gradient(
68
+ to right,
69
+ var(--ef-volume-fill-color) var(--_fill-pct, 100%),
70
+ var(--ef-volume-track-color) var(--_fill-pct, 100%)
71
+ );
72
+ height: var(--ef-volume-height);
73
+ border-radius: 2px;
74
+ }
75
+
76
+ input[type="range"]::-moz-range-track {
77
+ height: var(--ef-volume-height);
78
+ background: var(--ef-volume-track-color);
79
+ border-radius: 2px;
80
+ }
81
+
82
+ input[type="range"]::-moz-range-progress {
83
+ height: var(--ef-volume-height);
84
+ background: var(--ef-volume-fill-color);
85
+ border-radius: 2px;
86
+ }
87
+
88
+ ::part(slider) {}
89
+ `];
90
+ }
91
+ get context() {
92
+ return this.effectiveContext;
93
+ }
94
+ get displayVolume() {
95
+ return this.contextMuted ? 0 : this.contextVolume;
96
+ }
97
+ render() {
98
+ const pct = `${this.displayVolume * 100}%`;
99
+ return html`
100
+ <input
101
+ part="slider"
102
+ type="range"
103
+ min="0"
104
+ max="1"
105
+ step="0.01"
106
+ .value=${String(this.displayVolume)}
107
+ style="--_fill-pct: ${pct}"
108
+ @input=${this.handleInput}
109
+ />
110
+ `;
111
+ }
112
+ };
113
+ __decorate([consume({
114
+ context: volumeContext,
115
+ subscribe: true
116
+ }), state()], EFVolume.prototype, "contextVolume", void 0);
117
+ __decorate([consume({
118
+ context: mutedContext,
119
+ subscribe: true
120
+ }), state()], EFVolume.prototype, "contextMuted", void 0);
121
+ EFVolume = __decorate([customElement("ef-volume")], EFVolume);
122
+ //#endregion
123
+ export { EFVolume };
124
+
125
+ //# sourceMappingURL=EFVolume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EFVolume.js","names":[],"sources":["../../src/gui/EFVolume.ts"],"mappings":";;;;;;;;AA0BO,IAAA,WAAA,MAAM,iBAAiB,qBAAqB,YAAY,UAAU,CAAC;;;uBAwEhD;sBAID;sBAUA,MAAa;GAClC,MAAM,QAAQ,EAAE;GAChB,MAAM,QAAQ,OAAO,WAAW,MAAM,MAAM;AAC5C,OAAI,KAAK,SAAS;AAChB,QAAI,KAAK,QAAQ,SAAS,QAAQ,EAChC,MAAK,QAAQ,QAAQ;AAEvB,SAAK,QAAQ,SAAS;;;;;gBA5FV,CACd,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAkEJ;;CAUD,IAAI,UAAwC;AAC1C,SAAO,KAAK;;CAGd,IAAY,gBAAwB;AAClC,SAAO,KAAK,eAAe,IAAI,KAAK;;CActC,SAAS;EACP,MAAM,MAAM,GAAG,KAAK,gBAAgB,IAAI;AACxC,SAAO,IAAI;;;;;;;iBAOE,OAAO,KAAK,cAAc,CAAC;8BACd,IAAI;iBACjB,KAAK,YAAY;;;;;YAtC/B,QAAQ;CAAE,SAAS;CAAe,WAAW;CAAM,CAAC,EACpD,OAAO,CAAA,EAAA,SAAA,WAAA,iBAAA,KAAA,EAAA;YAGP,QAAQ;CAAE,SAAS;CAAc,WAAW;CAAM,CAAC,EACnD,OAAO,CAAA,EAAA,SAAA,WAAA,gBAAA,KAAA,EAAA;uBA5ET,cAAc,YAAY,CAAA,EAAA,SAAA"}
@@ -27,7 +27,7 @@ interface PlaybackHost extends HTMLElement, ReactiveControllerHost {
27
27
  rootTimegroup?: any;
28
28
  }
29
29
  type PlaybackControllerUpdateEvent = {
30
- property: "playing" | "loop" | "currentTimeMs";
30
+ property: "playing" | "loop" | "currentTimeMs" | "volume" | "muted";
31
31
  value: boolean | number;
32
32
  };
33
33
  /**
@@ -52,6 +52,10 @@ declare class PlaybackController implements ReactiveController {
52
52
  setPlaying(value: boolean): void;
53
53
  get loop(): boolean;
54
54
  setLoop(value: boolean): void;
55
+ get volume(): number;
56
+ setVolume(value: number): void;
57
+ get muted(): boolean;
58
+ setMuted(value: boolean): void;
55
59
  get currentTimeMs(): number;
56
60
  setCurrentTimeMs(value: number): void;
57
61
  play(): void;
@@ -1,6 +1,7 @@
1
1
  import { currentTimeContext } from "./currentTimeContext.js";
2
2
  import { durationContext } from "./durationContext.js";
3
3
  import { loopContext, playingContext } from "./playingContext.js";
4
+ import { mutedContext, volumeContext } from "./volumeContext.js";
4
5
  import { updateAnimations } from "../elements/updateAnimations.js";
5
6
  import { ContextProvider } from "@lit/context";
6
7
  //#region src/gui/PlaybackController.ts
@@ -21,14 +22,19 @@ var PlaybackController = class {
21
22
  #host;
22
23
  #playing = false;
23
24
  #loop = false;
25
+ #volume = 1;
26
+ #muted = false;
24
27
  #listeners = /* @__PURE__ */ new Set();
25
28
  #playingProvider;
26
29
  #loopProvider;
27
30
  #currentTimeMsProvider;
28
31
  #durationMsProvider;
32
+ #volumeProvider;
33
+ #mutedProvider;
29
34
  get #msPerFrame() {
30
35
  return 1e3 / (this.#host.fps ?? 30);
31
36
  }
37
+ #gainNode = null;
32
38
  #playbackAudioContext = null;
33
39
  #playbackAnimationFrameRequest = null;
34
40
  #pendingAudioContext = null;
@@ -62,6 +68,14 @@ var PlaybackController = class {
62
68
  context: durationContext,
63
69
  initialValue: host.durationMs
64
70
  });
71
+ this.#volumeProvider = new ContextProvider(host, {
72
+ context: volumeContext,
73
+ initialValue: this.#volume
74
+ });
75
+ this.#mutedProvider = new ContextProvider(host, {
76
+ context: mutedContext,
77
+ initialValue: this.#muted
78
+ });
65
79
  }
66
80
  get currentTime() {
67
81
  const rawTime = this.#currentTime ?? 0;
@@ -155,6 +169,37 @@ var PlaybackController = class {
155
169
  value
156
170
  });
157
171
  }
172
+ get volume() {
173
+ return this.#volume;
174
+ }
175
+ setVolume(value) {
176
+ const clamped = Math.max(0, Math.min(1, value));
177
+ if (this.#volume === clamped) return;
178
+ this.#volume = clamped;
179
+ this.#volumeProvider.setValue(clamped);
180
+ this.#notifyListeners({
181
+ property: "volume",
182
+ value: clamped
183
+ });
184
+ this.#applyGain();
185
+ }
186
+ get muted() {
187
+ return this.#muted;
188
+ }
189
+ setMuted(value) {
190
+ if (this.#muted === value) return;
191
+ this.#muted = value;
192
+ this.#mutedProvider.setValue(value);
193
+ this.#notifyListeners({
194
+ property: "muted",
195
+ value
196
+ });
197
+ this.#applyGain();
198
+ }
199
+ #applyGain() {
200
+ if (!this.#gainNode) return;
201
+ this.#gainNode.gain.value = this.#muted ? 0 : this.#volume;
202
+ }
158
203
  get currentTimeMs() {
159
204
  return this.currentTime * 1e3;
160
205
  }
@@ -345,6 +390,7 @@ var PlaybackController = class {
345
390
  this.#playbackAnimationFrameRequest = null;
346
391
  }
347
392
  this.#rafStartWallMs = null;
393
+ this.#gainNode = null;
348
394
  if (this.#playbackAudioContext) {
349
395
  if (this.#playbackAudioContext.state !== "closed") await this.#playbackAudioContext.close();
350
396
  }
@@ -390,6 +436,9 @@ var PlaybackController = class {
390
436
  this.#playbackAudioContext = this.#pendingAudioContext;
391
437
  this.#pendingAudioContext = null;
392
438
  } else this.#playbackAudioContext = new AudioContext({ latencyHint: "playback" });
439
+ this.#gainNode = this.#playbackAudioContext.createGain();
440
+ this.#gainNode.gain.value = this.#muted ? 0 : this.#volume;
441
+ this.#gainNode.connect(this.#playbackAudioContext.destination);
393
442
  const playbackContext = this.#playbackAudioContext;
394
443
  if (playbackContext.state === "suspended") {
395
444
  playbackContext.close().catch(() => {});
@@ -419,7 +468,7 @@ var PlaybackController = class {
419
468
  bufferCount++;
420
469
  const source = playbackContext.createBufferSource();
421
470
  source.buffer = audioBuffer;
422
- source.connect(playbackContext.destination);
471
+ source.connect(this.#gainNode ?? playbackContext.destination);
423
472
  source.start(audioContextTimeMs / 1e3);
424
473
  const sliceDurationMs = endMs - startMs;
425
474
  source.onended = () => {