@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.
- package/dist/elements/EFTemporal.d.ts +2 -0
- package/dist/elements/EFTemporal.js +12 -0
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/gui/ContextMixin.d.ts +2 -0
- package/dist/gui/ContextMixin.js +44 -0
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.d.ts +2 -0
- package/dist/gui/Controllable.js +6 -0
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -0
- package/dist/gui/EFControls.js +14 -1
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFFullscreen.d.ts +38 -0
- package/dist/gui/EFFullscreen.js +50 -0
- package/dist/gui/EFFullscreen.js.map +1 -0
- package/dist/gui/EFMute.d.ts +40 -0
- package/dist/gui/EFMute.js +41 -0
- package/dist/gui/EFMute.js.map +1 -0
- package/dist/gui/EFPIP.d.ts +43 -0
- package/dist/gui/EFPIP.js +89 -0
- package/dist/gui/EFPIP.js.map +1 -0
- package/dist/gui/EFResolution.d.ts +47 -0
- package/dist/gui/EFResolution.js +203 -0
- package/dist/gui/EFResolution.js.map +1 -0
- package/dist/gui/EFVolume.d.ts +44 -0
- package/dist/gui/EFVolume.js +125 -0
- package/dist/gui/EFVolume.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +5 -1
- package/dist/gui/PlaybackController.js +50 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/icons.js +5 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/volumeContext.d.ts +10 -0
- package/dist/gui/volumeContext.js +8 -0
- package/dist/gui/volumeContext.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/version.js +1 -1
- 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 = () => {
|