@hyperframes/studio 0.5.0-alpha.8 → 0.5.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/assets/hyperframes-player-CoI5h1xv.js +353 -0
- package/dist/assets/index-BKjcNNNd.css +1 -0
- package/dist/assets/index-CqiisJmo.js +93 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +208 -1436
- package/src/captions/components/CaptionOverlay.tsx +2 -1
- package/src/captions/generator.test.ts +19 -0
- package/src/captions/generator.ts +9 -2
- package/src/captions/hooks/useCaptionSync.ts +6 -1
- package/src/captions/keyboard.test.ts +38 -0
- package/src/captions/keyboard.ts +8 -0
- package/src/captions/parser.test.ts +14 -0
- package/src/captions/parser.ts +1 -0
- package/src/components/LintModal.tsx +4 -3
- package/src/components/editor/PropertyPanel.tsx +206 -2462
- package/src/components/nle/NLELayout.tsx +47 -17
- package/src/components/nle/NLEPreview.tsx +9 -50
- package/src/components/sidebar/AssetsTab.tsx +4 -3
- package/src/components/sidebar/CompositionsTab.test.ts +1 -16
- package/src/components/sidebar/CompositionsTab.tsx +45 -117
- package/src/components/sidebar/LeftSidebar.tsx +55 -34
- package/src/components/ui/HyperframesLoader.tsx +104 -0
- package/src/components/ui/index.ts +2 -0
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/CompositionThumbnail.tsx +10 -42
- package/src/player/components/EditModal.tsx +20 -5
- package/src/player/components/Player.tsx +129 -28
- package/src/player/components/PlayerControls.tsx +117 -49
- package/src/player/components/Timeline.test.ts +0 -12
- package/src/player/components/Timeline.tsx +25 -52
- package/src/player/components/TimelineClip.tsx +9 -21
- package/src/player/components/timelineEditing.test.ts +4 -2
- package/src/player/components/timelineEditing.ts +3 -1
- package/src/player/components/timelineTheme.test.ts +19 -0
- package/src/player/components/timelineTheme.ts +8 -4
- package/src/player/hooks/useTimelinePlayer.test.ts +219 -1
- package/src/player/hooks/useTimelinePlayer.ts +487 -106
- package/src/player/lib/time.test.ts +29 -1
- package/src/player/lib/time.ts +26 -0
- package/src/player/store/playerStore.test.ts +11 -1
- package/src/player/store/playerStore.ts +6 -1
- package/src/styles/studio.css +112 -0
- package/src/utils/frameCapture.test.ts +26 -0
- package/src/utils/frameCapture.ts +40 -0
- package/src/utils/mediaTypes.ts +1 -1
- package/src/utils/projectRouting.test.ts +87 -0
- package/src/utils/projectRouting.ts +27 -0
- package/src/utils/sourcePatcher.test.ts +1 -128
- package/src/utils/sourcePatcher.ts +18 -130
- package/src/utils/timelineAssetDrop.test.ts +11 -31
- package/src/utils/timelineAssetDrop.ts +2 -22
- package/dist/assets/hyperframes-player-vibA20NC.js +0 -198
- package/dist/assets/index-0Zt0t13W.css +0 -1
- package/dist/assets/index-C9f5eif8.js +0 -105
- package/src/components/editor/DomEditOverlay.tsx +0 -442
- package/src/components/editor/colorValue.test.ts +0 -82
- package/src/components/editor/colorValue.ts +0 -175
- package/src/components/editor/domEditing.test.ts +0 -537
- package/src/components/editor/domEditing.ts +0 -762
- package/src/components/editor/floatingPanel.test.ts +0 -34
- package/src/components/editor/floatingPanel.ts +0 -54
- package/src/components/editor/fontAssets.ts +0 -32
- package/src/components/editor/fontCatalog.ts +0 -126
- package/src/components/editor/gradientValue.test.ts +0 -89
- package/src/components/editor/gradientValue.ts +0 -445
- package/src/player/components/CompositionThumbnail.test.ts +0 -19
- package/src/utils/clipboard.test.ts +0 -88
- package/src/utils/clipboard.ts +0 -57
|
@@ -7,67 +7,6 @@ function escapeRegex(s: string): string {
|
|
|
7
7
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function escapeStyleAttributeValue(value: string, quote: string): string {
|
|
11
|
-
return quote === '"' ? value.replace(/"/g, """) : value.replace(/'/g, "'");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function splitInlineStyleDeclarations(style: string): string[] {
|
|
15
|
-
const declarations: string[] = [];
|
|
16
|
-
let current = "";
|
|
17
|
-
let quote: string | null = null;
|
|
18
|
-
let entity = false;
|
|
19
|
-
let parenDepth = 0;
|
|
20
|
-
|
|
21
|
-
for (const char of style) {
|
|
22
|
-
if (entity) {
|
|
23
|
-
current += char;
|
|
24
|
-
if (char === ";") entity = false;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (char === "&") {
|
|
29
|
-
entity = true;
|
|
30
|
-
current += char;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (quote) {
|
|
35
|
-
current += char;
|
|
36
|
-
if (char === quote) quote = null;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (char === '"' || char === "'") {
|
|
41
|
-
quote = char;
|
|
42
|
-
current += char;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (char === "(") {
|
|
47
|
-
parenDepth += 1;
|
|
48
|
-
current += char;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (char === ")") {
|
|
53
|
-
parenDepth = Math.max(0, parenDepth - 1);
|
|
54
|
-
current += char;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (char === ";" && parenDepth === 0) {
|
|
59
|
-
declarations.push(current);
|
|
60
|
-
current = "";
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
current += char;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (current) declarations.push(current);
|
|
68
|
-
return declarations;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
10
|
export interface PatchOperation {
|
|
72
11
|
type: "inline-style" | "attribute" | "text-content";
|
|
73
12
|
property: string;
|
|
@@ -135,7 +74,7 @@ export function resolveSourceFile(
|
|
|
135
74
|
*/
|
|
136
75
|
function patchInlineStyle(html: string, elementId: string, prop: string, value: string): string {
|
|
137
76
|
// Find the element tag with this id
|
|
138
|
-
const idPattern = new RegExp(`(<[^>]*\\bid=
|
|
77
|
+
const idPattern = new RegExp(`(<[^>]*\\bid="${escapeRegex(elementId)}"[^>]*)>`, "i");
|
|
139
78
|
const match = idPattern.exec(html);
|
|
140
79
|
if (!match) return html;
|
|
141
80
|
|
|
@@ -147,13 +86,12 @@ function patchInlineStyleInTag(html: string, tag: string, prop: string, value: s
|
|
|
147
86
|
if (!tag) return html;
|
|
148
87
|
|
|
149
88
|
// Check if there's an existing style attribute
|
|
150
|
-
const styleMatch = /\bstyle=(["
|
|
89
|
+
const styleMatch = /\bstyle="([^"]*)"/.exec(tag);
|
|
151
90
|
if (styleMatch) {
|
|
152
|
-
const existingStyle = styleMatch[
|
|
153
|
-
const quote = styleMatch[1];
|
|
91
|
+
const existingStyle = styleMatch[1];
|
|
154
92
|
// Parse existing properties
|
|
155
93
|
const props = new Map<string, string>();
|
|
156
|
-
for (const part of
|
|
94
|
+
for (const part of existingStyle.split(";")) {
|
|
157
95
|
const colon = part.indexOf(":");
|
|
158
96
|
if (colon < 0) continue;
|
|
159
97
|
const key = part.slice(0, colon).trim();
|
|
@@ -164,14 +102,13 @@ function patchInlineStyleInTag(html: string, tag: string, prop: string, value: s
|
|
|
164
102
|
props.set(prop, value);
|
|
165
103
|
// Rebuild style string
|
|
166
104
|
const newStyle = Array.from(props.entries())
|
|
167
|
-
.map(([k, v]) => `${k}: ${
|
|
105
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
168
106
|
.join("; ");
|
|
169
|
-
const newTag = tag.replace(
|
|
107
|
+
const newTag = tag.replace(/\bstyle="[^"]*"/, `style="${newStyle}"`);
|
|
170
108
|
return html.replace(tag, newTag);
|
|
171
109
|
} else {
|
|
172
110
|
// No existing style — add one
|
|
173
|
-
const newTag =
|
|
174
|
-
tag.replace(/>$/, "") + ` style="${prop}: ${escapeStyleAttributeValue(value, '"')}"`;
|
|
111
|
+
const newTag = tag.replace(/>$/, "") + ` style="${prop}: ${value}"`;
|
|
175
112
|
return html.replace(tag, newTag);
|
|
176
113
|
}
|
|
177
114
|
}
|
|
@@ -200,7 +137,7 @@ function replaceTagAtMatch(html: string, match: TagMatch, newTag: string): strin
|
|
|
200
137
|
|
|
201
138
|
function findTagByTarget(html: string, target: PatchTarget): TagMatch | null {
|
|
202
139
|
if (target.id) {
|
|
203
|
-
const idPattern = new RegExp(`(<[^>]*\\bid=
|
|
140
|
+
const idPattern = new RegExp(`(<[^>]*\\bid="${escapeRegex(target.id)}"[^>]*)>`, "i");
|
|
204
141
|
const match = idPattern.exec(html);
|
|
205
142
|
if (match?.index != null) {
|
|
206
143
|
return {
|
|
@@ -217,7 +154,7 @@ function findTagByTarget(html: string, target: PatchTarget): TagMatch | null {
|
|
|
217
154
|
if (compositionIdMatch) {
|
|
218
155
|
const compId = compositionIdMatch[1];
|
|
219
156
|
const pattern = new RegExp(
|
|
220
|
-
`(<[^>]*\\bdata-composition-id=
|
|
157
|
+
`(<[^>]*\\bdata-composition-id="${escapeRegex(compId)}"[^>]*)>`,
|
|
221
158
|
"i",
|
|
222
159
|
);
|
|
223
160
|
const match = pattern.exec(html);
|
|
@@ -264,13 +201,8 @@ export function readAttributeByTarget(
|
|
|
264
201
|
if (!match) return undefined;
|
|
265
202
|
|
|
266
203
|
const fullAttr = attr.startsWith("data-") ? attr : `data-${attr}`;
|
|
267
|
-
const valueMatch = new RegExp(`\\b${fullAttr}=
|
|
268
|
-
return valueMatch?.[
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function readTagSnippetByTarget(html: string, target: PatchTarget): string | undefined {
|
|
272
|
-
const match = findTagByTarget(html, target);
|
|
273
|
-
return match?.tag;
|
|
204
|
+
const valueMatch = new RegExp(`\\b${fullAttr}="([^"]*)"`).exec(match.tag);
|
|
205
|
+
return valueMatch?.[1];
|
|
274
206
|
}
|
|
275
207
|
|
|
276
208
|
function patchAttributeByTarget(
|
|
@@ -283,7 +215,7 @@ function patchAttributeByTarget(
|
|
|
283
215
|
if (!match) return html;
|
|
284
216
|
|
|
285
217
|
const fullAttr = attr.startsWith("data-") ? attr : `data-${attr}`;
|
|
286
|
-
const attrPattern = new RegExp(`\\b${fullAttr}=
|
|
218
|
+
const attrPattern = new RegExp(`\\b${fullAttr}="[^"]*"`);
|
|
287
219
|
const tag = match.tag;
|
|
288
220
|
|
|
289
221
|
if (attrPattern.test(tag)) {
|
|
@@ -299,13 +231,13 @@ function patchAttributeByTarget(
|
|
|
299
231
|
* Apply an attribute change to an element in the HTML source.
|
|
300
232
|
*/
|
|
301
233
|
function patchAttribute(html: string, elementId: string, attr: string, value: string): string {
|
|
302
|
-
const idPattern = new RegExp(`(<[^>]*\\bid=
|
|
234
|
+
const idPattern = new RegExp(`(<[^>]*\\bid="${escapeRegex(elementId)}"[^>]*)>`, "i");
|
|
303
235
|
const match = idPattern.exec(html);
|
|
304
236
|
if (!match) return html;
|
|
305
237
|
|
|
306
238
|
const tag = match[1];
|
|
307
239
|
const fullAttr = attr.startsWith("data-") ? attr : `data-${attr}`;
|
|
308
|
-
const attrPattern = new RegExp(`\\b${fullAttr}=
|
|
240
|
+
const attrPattern = new RegExp(`\\b${fullAttr}="[^"]*"`);
|
|
309
241
|
|
|
310
242
|
if (attrPattern.test(tag)) {
|
|
311
243
|
// Update existing attribute
|
|
@@ -322,53 +254,11 @@ function patchAttribute(html: string, elementId: string, attr: string, value: st
|
|
|
322
254
|
* Apply a text content change to an element.
|
|
323
255
|
*/
|
|
324
256
|
function patchTextContent(html: string, elementId: string, value: string): string {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
);
|
|
329
|
-
const match = openTagPattern.exec(html);
|
|
330
|
-
if (!match || match.index == null) return html;
|
|
331
|
-
|
|
332
|
-
const openingTag = match[1];
|
|
333
|
-
const tagName = match[2];
|
|
334
|
-
const contentStart = match.index + openingTag.length;
|
|
335
|
-
const closingIndex = findMatchingClosingTagIndex(html, tagName, contentStart);
|
|
336
|
-
if (closingIndex < 0) return html;
|
|
337
|
-
|
|
338
|
-
return `${html.slice(0, contentStart)}${value}${html.slice(closingIndex)}`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function findMatchingClosingTagIndex(html: string, tagName: string, contentStart: number): number {
|
|
342
|
-
const tagPattern = new RegExp(`</?${escapeRegex(tagName)}\\b[^>]*>`, "gi");
|
|
343
|
-
tagPattern.lastIndex = contentStart;
|
|
344
|
-
let depth = 1;
|
|
345
|
-
let match: RegExpExecArray | null;
|
|
346
|
-
|
|
347
|
-
while ((match = tagPattern.exec(html)) !== null) {
|
|
348
|
-
const tag = match[0];
|
|
349
|
-
if (tag.startsWith("</")) {
|
|
350
|
-
depth -= 1;
|
|
351
|
-
if (depth === 0) return match.index;
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (!tag.endsWith("/>")) depth += 1;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return -1;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function patchTextContentByTarget(html: string, target: PatchTarget, value: string): string {
|
|
361
|
-
const match = findTagByTarget(html, target);
|
|
257
|
+
// Match the element and its content: <tagname id="elementId"...>content</tagname>
|
|
258
|
+
const pattern = new RegExp(`(<[^>]*\\bid="${elementId}"[^>]*>)([\\s\\S]*?)(<\\/[a-z]+>)`, "i");
|
|
259
|
+
const match = pattern.exec(html);
|
|
362
260
|
if (!match) return html;
|
|
363
|
-
|
|
364
|
-
const tagNameMatch = /^<([a-z0-9-]+)/i.exec(match.tag);
|
|
365
|
-
const tagName = tagNameMatch?.[1];
|
|
366
|
-
if (!tagName) return html;
|
|
367
|
-
|
|
368
|
-
const closingIndex = findMatchingClosingTagIndex(html, tagName, match.end + 1);
|
|
369
|
-
if (closingIndex < 0) return html;
|
|
370
|
-
|
|
371
|
-
return `${html.slice(0, match.end + 1)}${value}${html.slice(closingIndex)}`;
|
|
261
|
+
return html.replace(pattern, `${match[1]}${value}${match[3]}`);
|
|
372
262
|
}
|
|
373
263
|
|
|
374
264
|
/**
|
|
@@ -400,8 +290,6 @@ export function applyPatchByTarget(html: string, target: PatchTarget, op: PatchO
|
|
|
400
290
|
return patchInlineStyleByTarget(html, target, op.property, op.value);
|
|
401
291
|
case "attribute":
|
|
402
292
|
return patchAttributeByTarget(html, target, op.property, op.value);
|
|
403
|
-
case "text-content":
|
|
404
|
-
return patchTextContentByTarget(html, target, op.value);
|
|
405
293
|
default:
|
|
406
294
|
return html;
|
|
407
295
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
buildTimelineAssetInsertHtml,
|
|
5
5
|
getTimelineAssetKind,
|
|
6
6
|
insertTimelineAssetIntoSource,
|
|
7
|
-
resolveTimelineAssetInitialGeometry,
|
|
8
7
|
resolveTimelineAssetSrc,
|
|
9
8
|
} from "./timelineAssetDrop";
|
|
10
9
|
|
|
@@ -20,21 +19,17 @@ describe("getTimelineAssetKind", () => {
|
|
|
20
19
|
|
|
21
20
|
describe("buildTimelineAssetInsertHtml", () => {
|
|
22
21
|
it("builds an image clip with explicit timing and track", () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
expect(html).toContain('img id="photo_asset"');
|
|
35
|
-
expect(html).toContain("left: 0px");
|
|
36
|
-
expect(html).toContain("width: 1280px");
|
|
37
|
-
expect(html).not.toContain("inset:");
|
|
22
|
+
expect(
|
|
23
|
+
buildTimelineAssetInsertHtml({
|
|
24
|
+
id: "photo_asset",
|
|
25
|
+
assetPath: "assets/photo.png",
|
|
26
|
+
kind: "image",
|
|
27
|
+
start: 1.25,
|
|
28
|
+
duration: 3,
|
|
29
|
+
track: 2,
|
|
30
|
+
zIndex: 4,
|
|
31
|
+
}),
|
|
32
|
+
).toContain('img id="photo_asset"');
|
|
38
33
|
});
|
|
39
34
|
|
|
40
35
|
it("builds an audio clip without visual layout styles", () => {
|
|
@@ -52,21 +47,6 @@ describe("buildTimelineAssetInsertHtml", () => {
|
|
|
52
47
|
});
|
|
53
48
|
});
|
|
54
49
|
|
|
55
|
-
describe("resolveTimelineAssetInitialGeometry", () => {
|
|
56
|
-
it("uses the target composition dimensions for visual media", () => {
|
|
57
|
-
expect(
|
|
58
|
-
resolveTimelineAssetInitialGeometry(
|
|
59
|
-
`<div data-composition-id="main" data-width="330" data-height="228"></div>`,
|
|
60
|
-
),
|
|
61
|
-
).toEqual({
|
|
62
|
-
left: 0,
|
|
63
|
-
top: 0,
|
|
64
|
-
width: 330,
|
|
65
|
-
height: 228,
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
50
|
describe("resolveTimelineAssetSrc", () => {
|
|
71
51
|
it("keeps project-root asset paths for index.html", () => {
|
|
72
52
|
expect(resolveTimelineAssetSrc("index.html", "assets/photo.png")).toBe("assets/photo.png");
|
|
@@ -76,23 +76,6 @@ export function buildTimelineFileDropPlacements(
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export function resolveTimelineAssetInitialGeometry(source: string): {
|
|
80
|
-
left: number;
|
|
81
|
-
top: number;
|
|
82
|
-
width: number;
|
|
83
|
-
height: number;
|
|
84
|
-
} {
|
|
85
|
-
const width = Number.parseFloat(source.match(/\bdata-width=(["'])([^"']+)\1/i)?.[2] ?? "");
|
|
86
|
-
const height = Number.parseFloat(source.match(/\bdata-height=(["'])([^"']+)\1/i)?.[2] ?? "");
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
left: 0,
|
|
90
|
-
top: 0,
|
|
91
|
-
width: Number.isFinite(width) && width > 0 ? Math.round(width) : 640,
|
|
92
|
-
height: Number.isFinite(height) && height > 0 ? Math.round(height) : 360,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
79
|
export function buildTimelineAssetInsertHtml(input: {
|
|
97
80
|
id: string;
|
|
98
81
|
assetPath: string;
|
|
@@ -101,18 +84,15 @@ export function buildTimelineAssetInsertHtml(input: {
|
|
|
101
84
|
duration: number;
|
|
102
85
|
track: number;
|
|
103
86
|
zIndex: number;
|
|
104
|
-
geometry?: { left: number; top: number; width: number; height: number };
|
|
105
87
|
}): string {
|
|
106
88
|
const sharedAttrs = `id="${input.id}" class="clip" src="${input.assetPath}" data-start="${input.start}" data-duration="${input.duration}" data-track-index="${input.track}"`;
|
|
107
|
-
const geometry = input.geometry ?? { left: 0, top: 0, width: 640, height: 360 };
|
|
108
|
-
const visualStyles = `position: absolute; left: ${geometry.left}px; top: ${geometry.top}px; width: ${geometry.width}px; height: ${geometry.height}px; object-fit: contain; z-index: ${input.zIndex}`;
|
|
109
89
|
|
|
110
90
|
if (input.kind === "image") {
|
|
111
|
-
return `<img ${sharedAttrs} style="${
|
|
91
|
+
return `<img ${sharedAttrs} style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: ${input.zIndex}" />`;
|
|
112
92
|
}
|
|
113
93
|
|
|
114
94
|
if (input.kind === "video") {
|
|
115
|
-
return `<video ${sharedAttrs} muted playsinline style="${
|
|
95
|
+
return `<video ${sharedAttrs} muted playsinline style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: ${input.zIndex}"></video>`;
|
|
116
96
|
}
|
|
117
97
|
|
|
118
98
|
return `<audio ${sharedAttrs} style="z-index: ${input.zIndex}"></audio>`;
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
var D=Object.defineProperty;var F=(c,b,e)=>b in c?D(c,b,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[b]=e;var p=(c,b,e)=>F(c,typeof b!="symbol"?b+"":b,e);const N=`
|
|
2
|
-
:host {
|
|
3
|
-
display: block;
|
|
4
|
-
position: relative;
|
|
5
|
-
overflow: hidden;
|
|
6
|
-
background: #000;
|
|
7
|
-
contain: layout style;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.hfp-container {
|
|
11
|
-
position: absolute;
|
|
12
|
-
inset: 0;
|
|
13
|
-
overflow: hidden;
|
|
14
|
-
pointer-events: none;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.hfp-iframe {
|
|
19
|
-
position: absolute;
|
|
20
|
-
top: 50%;
|
|
21
|
-
left: 50%;
|
|
22
|
-
border: none;
|
|
23
|
-
pointer-events: none;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.hfp-poster {
|
|
27
|
-
position: absolute;
|
|
28
|
-
inset: 0;
|
|
29
|
-
object-fit: contain;
|
|
30
|
-
z-index: 1;
|
|
31
|
-
pointer-events: none;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/* ── Theming via CSS custom properties ──
|
|
35
|
-
*
|
|
36
|
-
* Override from outside the shadow DOM:
|
|
37
|
-
* hyperframes-player {
|
|
38
|
-
* --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));
|
|
39
|
-
* --hfp-accent: #ff6b6b;
|
|
40
|
-
* --hfp-font: "Inter", sans-serif;
|
|
41
|
-
* }
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
.hfp-controls {
|
|
45
|
-
position: absolute;
|
|
46
|
-
bottom: 0;
|
|
47
|
-
left: 0;
|
|
48
|
-
right: 0;
|
|
49
|
-
display: flex;
|
|
50
|
-
align-items: center;
|
|
51
|
-
gap: var(--hfp-controls-gap, 12px);
|
|
52
|
-
padding: var(--hfp-controls-padding, 8px 16px);
|
|
53
|
-
background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));
|
|
54
|
-
color: var(--hfp-color, #fff);
|
|
55
|
-
font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);
|
|
56
|
-
font-size: var(--hfp-font-size, 13px);
|
|
57
|
-
z-index: 10;
|
|
58
|
-
pointer-events: auto;
|
|
59
|
-
opacity: 1;
|
|
60
|
-
transition: opacity 0.3s ease;
|
|
61
|
-
user-select: none;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.hfp-controls.hfp-hidden {
|
|
65
|
-
opacity: 0;
|
|
66
|
-
pointer-events: none;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.hfp-play-btn {
|
|
70
|
-
background: none;
|
|
71
|
-
border: none;
|
|
72
|
-
color: var(--hfp-color, #fff);
|
|
73
|
-
cursor: pointer;
|
|
74
|
-
padding: 8px;
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
justify-content: center;
|
|
78
|
-
width: 40px;
|
|
79
|
-
height: 40px;
|
|
80
|
-
flex-shrink: 0;
|
|
81
|
-
z-index: 10;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.hfp-play-btn:hover {
|
|
85
|
-
opacity: 0.8;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.hfp-play-btn svg,
|
|
89
|
-
.hfp-play-btn svg * {
|
|
90
|
-
pointer-events: none;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.hfp-scrubber {
|
|
94
|
-
flex: 1;
|
|
95
|
-
height: var(--hfp-scrubber-height, 4px);
|
|
96
|
-
background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));
|
|
97
|
-
border-radius: var(--hfp-scrubber-radius, 2px);
|
|
98
|
-
cursor: pointer;
|
|
99
|
-
position: relative;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.hfp-scrubber:hover {
|
|
103
|
-
height: var(--hfp-scrubber-height-hover, 6px);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.hfp-progress {
|
|
107
|
-
position: absolute;
|
|
108
|
-
top: 0;
|
|
109
|
-
left: 0;
|
|
110
|
-
height: 100%;
|
|
111
|
-
background: var(--hfp-accent, #fff);
|
|
112
|
-
border-radius: var(--hfp-scrubber-radius, 2px);
|
|
113
|
-
pointer-events: none;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.hfp-time {
|
|
117
|
-
flex-shrink: 0;
|
|
118
|
-
font-variant-numeric: tabular-nums;
|
|
119
|
-
opacity: 0.9;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.hfp-speed-wrap {
|
|
123
|
-
position: relative;
|
|
124
|
-
flex-shrink: 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.hfp-speed-btn {
|
|
128
|
-
background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));
|
|
129
|
-
border: none;
|
|
130
|
-
border-radius: var(--hfp-speed-btn-radius, 4px);
|
|
131
|
-
color: var(--hfp-color, #fff);
|
|
132
|
-
cursor: pointer;
|
|
133
|
-
font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);
|
|
134
|
-
font-size: 12px;
|
|
135
|
-
font-variant-numeric: tabular-nums;
|
|
136
|
-
font-weight: 600;
|
|
137
|
-
padding: 4px 8px;
|
|
138
|
-
min-width: 40px;
|
|
139
|
-
text-align: center;
|
|
140
|
-
transition: background 0.15s ease;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.hfp-speed-btn:hover {
|
|
144
|
-
background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.hfp-speed-menu {
|
|
148
|
-
position: absolute;
|
|
149
|
-
bottom: calc(100% + 8px);
|
|
150
|
-
right: 0;
|
|
151
|
-
background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));
|
|
152
|
-
backdrop-filter: blur(12px);
|
|
153
|
-
-webkit-backdrop-filter: blur(12px);
|
|
154
|
-
border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));
|
|
155
|
-
border-radius: var(--hfp-menu-radius, 8px);
|
|
156
|
-
padding: 4px;
|
|
157
|
-
display: flex;
|
|
158
|
-
flex-direction: column;
|
|
159
|
-
gap: 2px;
|
|
160
|
-
min-width: 80px;
|
|
161
|
-
opacity: 0;
|
|
162
|
-
visibility: hidden;
|
|
163
|
-
transform: translateY(4px);
|
|
164
|
-
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
|
|
165
|
-
box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.hfp-speed-menu.hfp-open {
|
|
169
|
-
opacity: 1;
|
|
170
|
-
visibility: visible;
|
|
171
|
-
transform: translateY(0);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.hfp-speed-option {
|
|
175
|
-
background: none;
|
|
176
|
-
border: none;
|
|
177
|
-
border-radius: 4px;
|
|
178
|
-
color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));
|
|
179
|
-
cursor: pointer;
|
|
180
|
-
font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);
|
|
181
|
-
font-size: 13px;
|
|
182
|
-
font-variant-numeric: tabular-nums;
|
|
183
|
-
padding: 6px 12px;
|
|
184
|
-
text-align: left;
|
|
185
|
-
transition: background 0.1s ease, color 0.1s ease;
|
|
186
|
-
white-space: nowrap;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.hfp-speed-option:hover {
|
|
190
|
-
background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));
|
|
191
|
-
color: var(--hfp-color, #fff);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.hfp-speed-option.hfp-active {
|
|
195
|
-
color: var(--hfp-accent, #fff);
|
|
196
|
-
font-weight: 600;
|
|
197
|
-
}
|
|
198
|
-
`,O='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><polygon points="4,2 16,9 4,16"/></svg>',U='<svg width="24" height="24" viewBox="0 0 18 18" fill="currentColor"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>',j=[.25,.5,1,1.5,2,4];function k(c){return Number.isInteger(c)?`${c}x`:`${c}x`}function R(c){if(!Number.isFinite(c)||c<0)return"0:00";const b=Math.floor(c),e=Math.floor(b/60),t=b%60;return`${e}:${t.toString().padStart(2,"0")}`}function z(c,b,e={}){const t=e.speedPresets??j,s=document.createElement("div");s.className="hfp-controls",s.addEventListener("click",o=>{o.stopPropagation()});const i=document.createElement("button");i.className="hfp-play-btn",i.type="button",i.innerHTML=O,i.setAttribute("aria-label","Play");const r=document.createElement("div");r.className="hfp-scrubber";const n=document.createElement("div");n.className="hfp-progress",n.style.width="0%",r.appendChild(n);const d=document.createElement("span");d.className="hfp-time",d.textContent="0:00 / 0:00";const _=document.createElement("div");_.className="hfp-speed-wrap";const f=document.createElement("button");f.className="hfp-speed-btn",f.type="button",f.textContent="1x",f.setAttribute("aria-label","Playback speed");const a=document.createElement("div");a.className="hfp-speed-menu",a.setAttribute("role","menu");for(const o of t){const h=document.createElement("button");h.className="hfp-speed-option",h.type="button",h.setAttribute("role","menuitem"),h.dataset.speed=String(o),h.textContent=k(o),o===1&&h.classList.add("hfp-active"),a.appendChild(h)}_.appendChild(a),_.appendChild(f),s.appendChild(i),s.appendChild(r),s.appendChild(d),s.appendChild(_),c.appendChild(s);let l=!1,u=null;t.indexOf(1),i.addEventListener("click",o=>{o.stopPropagation(),l?b.onPause():b.onPlay()});const m=o=>{for(const h of a.querySelectorAll(".hfp-speed-option"))h.classList.toggle("hfp-active",h.dataset.speed===String(o))};f.addEventListener("click",o=>{o.stopPropagation();const h=a.classList.toggle("hfp-open");f.setAttribute("aria-expanded",String(h))}),a.addEventListener("click",o=>{o.stopPropagation();const h=o.target.closest(".hfp-speed-option");if(!h)return;const y=parseFloat(h.dataset.speed);t.indexOf(y),f.textContent=k(y),m(y),a.classList.remove("hfp-open"),f.setAttribute("aria-expanded","false"),b.onSpeedChange(y)});const v=()=>{a.classList.remove("hfp-open"),f.setAttribute("aria-expanded","false")};document.addEventListener("click",v);const E=o=>{const h=r.getBoundingClientRect(),y=Math.max(0,Math.min(1,(o-h.left)/h.width));b.onSeek(y)};let g=!1;r.addEventListener("mousedown",o=>{o.stopPropagation(),g=!0,E(o.clientX)});const A=o=>{g&&E(o.clientX)},C=()=>{g=!1};document.addEventListener("mousemove",A),document.addEventListener("mouseup",C),r.addEventListener("touchstart",o=>{g=!0;const h=o.touches[0];h&&E(h.clientX)},{passive:!0});const P=o=>{if(g){const h=o.touches[0];h&&E(h.clientX)}},I=()=>{g=!1};document.addEventListener("touchmove",P,{passive:!0}),document.addEventListener("touchend",I);const T=()=>{u&&clearTimeout(u),u=setTimeout(()=>{l&&s.classList.add("hfp-hidden")},3e3)},L=c instanceof ShadowRoot?c.host:c;return L.addEventListener("mousemove",()=>{s.classList.remove("hfp-hidden"),T()}),L.addEventListener("mouseleave",()=>{l&&s.classList.add("hfp-hidden")}),{updateTime(o,h){const y=h>0?o/h*100:0;n.style.width=`${y}%`,d.textContent=`${R(o)} / ${R(h)}`},updatePlaying(o){l=o,i.innerHTML=o?U:O,i.setAttribute("aria-label",o?"Pause":"Play"),o?T():s.classList.remove("hfp-hidden")},updateSpeed(o){t.indexOf(o),f.textContent=k(o),m(o)},show(){s.style.display=""},hide(){s.style.display="none"},destroy(){document.removeEventListener("mousemove",A),document.removeEventListener("mouseup",C),document.removeEventListener("touchmove",P),document.removeEventListener("touchend",I),document.removeEventListener("click",v),u&&clearTimeout(u)}}}function H(c){return c.hasRuntime||c.runtimeInjected?!1:!!(c.hasNestedCompositions||c.hasTimelines&&c.attempts>=5)}let M=null;function q(){if(M)return M;if(typeof CSSStyleSheet>"u")return null;try{const c=new CSSStyleSheet;return c.replaceSync(N),M=c,c}catch{return null}}const S=30,W="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js",w=class w extends HTMLElement{constructor(){super();p(this,"shadow");p(this,"container");p(this,"iframe");p(this,"posterEl",null);p(this,"controlsApi",null);p(this,"resizeObserver");p(this,"_ready",!1);p(this,"_duration",0);p(this,"_currentTime",0);p(this,"_paused",!0);p(this,"_compositionWidth",1920);p(this,"_compositionHeight",1080);p(this,"_probeInterval",null);p(this,"_lastUpdateMs",0);p(this,"_parentMedia",[]);p(this,"_audioOwner","runtime");p(this,"_mediaObserver");p(this,"_playbackErrorPosted",!1);p(this,"_runtimeInjected",!1);this.shadow=this.attachShadow({mode:"open"});const e=q();if(e)this.shadow.adoptedStyleSheets=[e];else{const t=document.createElement("style");t.textContent=N,this.shadow.appendChild(t)}this.container=document.createElement("div"),this.container.className="hfp-container",this.iframe=document.createElement("iframe"),this.iframe.className="hfp-iframe",this.iframe.sandbox.add("allow-scripts","allow-same-origin"),this.iframe.allow="autoplay; fullscreen",this.iframe.referrerPolicy="no-referrer",this.iframe.title="HyperFrames Composition",this.container.appendChild(this.iframe),this.shadow.appendChild(this.container),this.addEventListener("click",t=>{this._isControlsClick(t)||(this._paused?this.play():this.pause())}),this.resizeObserver=new ResizeObserver(()=>this._updateScale()),this._onMessage=this._onMessage.bind(this),this._onIframeLoad=this._onIframeLoad.bind(this)}static get observedAttributes(){return["src","srcdoc","width","height","controls","muted","poster","playback-rate","audio-src"]}connectedCallback(){this.resizeObserver.observe(this),window.addEventListener("message",this._onMessage),this.iframe.addEventListener("load",this._onIframeLoad),this.hasAttribute("controls")&&this._setupControls(),this.hasAttribute("poster")&&this._setupPoster(),this.hasAttribute("audio-src")&&this._setupParentAudioFromUrl(this.getAttribute("audio-src")),this.hasAttribute("srcdoc")&&(this.iframe.srcdoc=this.getAttribute("srcdoc")),this.hasAttribute("src")&&(this.iframe.src=this.getAttribute("src"))}disconnectedCallback(){var e;this.resizeObserver.disconnect(),window.removeEventListener("message",this._onMessage),this.iframe.removeEventListener("load",this._onIframeLoad),this._probeInterval&&clearInterval(this._probeInterval),this._teardownMediaObserver(),(e=this.controlsApi)==null||e.destroy();for(const t of this._parentMedia)t.el.pause(),t.el.src="";this._parentMedia=[]}attributeChangedCallback(e,t,s){var i,r;switch(e){case"src":s&&(this._ready=!1,this.iframe.src=s);break;case"srcdoc":this._ready=!1,s!==null?this.iframe.srcdoc=s:this.iframe.removeAttribute("srcdoc");break;case"width":this._compositionWidth=parseInt(s||"1920",10),this._updateScale();break;case"height":this._compositionHeight=parseInt(s||"1080",10),this._updateScale();break;case"controls":s!==null?this._setupControls():((i=this.controlsApi)==null||i.destroy(),this.controlsApi=null);break;case"poster":this._setupPoster();break;case"playback-rate":{const n=parseFloat(s||"1");for(const d of this._parentMedia)d.el.playbackRate=n;this._sendControl("set-playback-rate",{playbackRate:n}),(r=this.controlsApi)==null||r.updateSpeed(n),this.dispatchEvent(new Event("ratechange"));break}case"muted":for(const n of this._parentMedia)n.el.muted=s!==null;this._sendControl("set-muted",{muted:s!==null});break;case"audio-src":s&&this._setupParentAudioFromUrl(s);break}}get iframeElement(){return this.iframe}play(){var e;this._hidePoster(),this._sendControl("play"),this._audioOwner==="parent"&&this._playParentMedia(),this._paused=!1,(e=this.controlsApi)==null||e.updatePlaying(!0),this.dispatchEvent(new Event("play"))}pause(){var e;this._sendControl("pause"),this._audioOwner==="parent"&&this._pauseParentMedia(),this._paused=!0,(e=this.controlsApi)==null||e.updatePlaying(!1),this.dispatchEvent(new Event("pause"))}seek(e){var t,s;if(!this._trySyncSeek(e)){const i=Math.round(e*S);this._sendControl("seek",{frame:i})}if(this._currentTime=e,this._audioOwner==="parent")for(const i of this._parentMedia){const r=e-i.start;r>=0&&r<i.duration&&(i.el.currentTime=r)}this._paused=!0,(t=this.controlsApi)==null||t.updatePlaying(!1),(s=this.controlsApi)==null||s.updateTime(this._currentTime,this._duration)}get currentTime(){return this._currentTime}set currentTime(e){this.seek(e)}get duration(){return this._duration}get paused(){return this._paused}get ready(){return this._ready}get playbackRate(){return parseFloat(this.getAttribute("playback-rate")||"1")}set playbackRate(e){this.setAttribute("playback-rate",String(e))}get muted(){return this.hasAttribute("muted")}set muted(e){e?this.setAttribute("muted",""):this.removeAttribute("muted")}get loop(){return this.hasAttribute("loop")}set loop(e){e?this.setAttribute("loop",""):this.removeAttribute("loop")}_sendControl(e,t={}){var s;try{(s=this.iframe.contentWindow)==null||s.postMessage({source:"hf-parent",type:"control",action:e,...t},"*")}catch{}}_trySyncSeek(e){try{const t=this.iframe.contentWindow,s=t==null?void 0:t.__player,i=s==null?void 0:s.seek;return typeof i!="function"?!1:(i.call(s,e),!0)}catch{return!1}}_isControlsClick(e){return e.composedPath().some(t=>t instanceof HTMLElement&&t.classList.contains("hfp-controls"))}_onMessage(e){var s,i,r,n;if(e.source!==this.iframe.contentWindow)return;const t=e.data;if(!(!t||t.source!=="hf-preview")){if(t.type==="state"){this._currentTime=(t.frame??0)/S;const d=!this._paused;this._paused=!t.isPlaying,this._audioOwner==="parent"&&(d&&this._paused?this._pauseParentMedia():!d&&!this._paused&&this._playParentMedia(),this._mirrorParentMediaTime(this._currentTime));const _=performance.now();(_-this._lastUpdateMs>100||this._paused!==d)&&(this._lastUpdateMs=_,(s=this.controlsApi)==null||s.updateTime(this._currentTime,this._duration),(i=this.controlsApi)==null||i.updatePlaying(!this._paused),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{currentTime:this._currentTime}}))),this._currentTime>=this._duration&&!this._paused&&(this._audioOwner==="parent"&&this._pauseParentMedia(),this.loop?(this.seek(0),this.play()):(this._paused=!0,(r=this.controlsApi)==null||r.updatePlaying(!1),this.dispatchEvent(new Event("ended"))))}t.type==="media-autoplay-blocked"&&this._promoteToParentProxy(),t.type==="timeline"&&t.durationInFrames>0&&Number.isFinite(t.durationInFrames)&&(this._duration=t.durationInFrames/S,(n=this.controlsApi)==null||n.updateTime(this._currentTime,this._duration)),t.type==="stage-size"&&t.width>0&&t.height>0&&(this._compositionWidth=t.width,this._compositionHeight=t.height,this._updateScale())}}_onIframeLoad(){let e=0;this._runtimeInjected=!1;const t=this._audioOwner==="parent";this._audioOwner="runtime",this._playbackErrorPosted=!1,this._pauseParentMedia(),this._teardownMediaObserver(),t&&this.dispatchEvent(new CustomEvent("audioownershipchange",{detail:{owner:"runtime",reason:"iframe-reload"}})),this._probeInterval&&clearInterval(this._probeInterval),this._probeInterval=setInterval(()=>{var s,i;e++;try{const r=this.iframe.contentWindow;if(!r)return;const n=!!(r.__hf||r.__player),d=!!(r.__timelines&&Object.keys(r.__timelines).length>0),_=!!((s=this.iframe.contentDocument)!=null&&s.querySelector("[data-composition-src]"));if(H({hasRuntime:n,hasTimelines:d,hasNestedCompositions:_,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!n)return;const a=(()=>{var l,u;if(r.__player&&typeof r.__player.getDuration=="function")return r.__player;if(r.__timelines){const m=Object.keys(r.__timelines);if(m.length>0){const v=(u=(l=this.iframe.contentDocument)==null?void 0:l.querySelector("[data-composition-id]"))==null?void 0:u.getAttribute("data-composition-id"),E=v&&v in r.__timelines?v:m[m.length-1],g=r.__timelines[E];return{getDuration:()=>g.duration()}}}return null})();if(a&&a.getDuration()>0){clearInterval(this._probeInterval),this._duration=a.getDuration(),this._ready=!0,(i=this.controlsApi)==null||i.updateTime(0,this._duration),this.dispatchEvent(new CustomEvent("ready",{detail:{duration:this._duration}}));const l=this.iframe.contentDocument,u=l==null?void 0:l.querySelector("[data-composition-id]");if(u){const m=parseInt(u.getAttribute("data-width")||"0",10),v=parseInt(u.getAttribute("data-height")||"0",10);m>0&&v>0&&(this._compositionWidth=m,this._compositionHeight=v,this._updateScale())}this._setupParentMedia(),this.hasAttribute("autoplay")&&this.play();return}}catch{}e>=40&&(clearInterval(this._probeInterval),this.dispatchEvent(new CustomEvent("error",{detail:{message:"Composition timeline not found after 8s"}})))},200)}_injectRuntime(){this._runtimeInjected=!0;try{const e=this.iframe.contentDocument;if(!e)return;const t=e.createElement("script");t.src=W,t.onload=()=>{},t.onerror=()=>{},(e.head||e.documentElement).appendChild(t)}catch{}}_updateScale(){const e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;const t=Math.min(e.width/this._compositionWidth,e.height/this._compositionHeight);this.iframe.style.width=`${this._compositionWidth}px`,this.iframe.style.height=`${this._compositionHeight}px`,this.iframe.style.transform=`translate(-50%, -50%) scale(${t})`}_setupControls(){if(this.controlsApi)return;const e={onPlay:()=>this.play(),onPause:()=>this.pause(),onSeek:i=>this.seek(i*this._duration),onSpeedChange:i=>{this.playbackRate=i}},t=this.getAttribute("speed-presets"),s=t?t.split(",").map(Number).filter(i=>!isNaN(i)&&i>0):void 0;this.controlsApi=z(this.shadow,e,{speedPresets:s})}_setupPoster(){var t;const e=this.getAttribute("poster");if(!e){(t=this.posterEl)==null||t.remove(),this.posterEl=null;return}this.posterEl||(this.posterEl=document.createElement("img"),this.posterEl.className="hfp-poster",this.shadow.appendChild(this.posterEl)),this.posterEl.src=e}_playParentMedia(){for(const e of this._parentMedia)e.el.src&&e.el.play().catch(t=>this._reportPlaybackError(t))}_reportPlaybackError(e){this._playbackErrorPosted||(this._playbackErrorPosted=!0,this.dispatchEvent(new CustomEvent("playbackerror",{detail:{source:"parent-proxy",error:e}})))}_pauseParentMedia(){for(const e of this._parentMedia)e.el.pause()}_mirrorParentMediaTime(e,t){const s=(t==null?void 0:t.force)===!0,i=w.MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES,r=w.MIRROR_DRIFT_THRESHOLD_SECONDS;for(const n of this._parentMedia){const d=e-n.start;if(d<0||d>=n.duration){n.driftSamples=0;continue}Math.abs(n.el.currentTime-d)>r?(n.driftSamples+=1,(s||n.driftSamples>=i)&&(n.el.currentTime=d,n.driftSamples=0)):n.driftSamples=0}}_promoteToParentProxy(){this._audioOwner!=="parent"&&(this._audioOwner="parent",this._sendControl("set-media-output-muted",{muted:!0}),this._mirrorParentMediaTime(this._currentTime,{force:!0}),this._paused||this._playParentMedia(),this.dispatchEvent(new CustomEvent("audioownershipchange",{detail:{owner:"parent",reason:"autoplay-blocked"}})))}_createParentMedia(e,t,s,i){if(this._parentMedia.some(d=>d.el.src===e))return null;const r=t==="video"?document.createElement("video"):new Audio;r.preload="auto",r.src=e,r.load(),r.muted=this.muted,this.playbackRate!==1&&(r.playbackRate=this.playbackRate);const n={el:r,start:s,duration:i,driftSamples:0};return this._parentMedia.push(n),n}_setupParentAudioFromUrl(e){this._createParentMedia(e,"audio",0,1/0)}_setupParentMedia(){try{const e=this.iframe.contentDocument;if(!e)return;const t=e.querySelectorAll("audio[data-start], video[data-start]");for(const s of t)this._adoptIframeMedia(s);this._observeDynamicMedia(e)}catch{}}_adoptIframeMedia(e){var _;const t=e.getAttribute("src")||((_=e.querySelector("source"))==null?void 0:_.getAttribute("src"));if(!t)return;const s=new URL(t,e.ownerDocument.baseURI).href,i=parseFloat(e.getAttribute("data-start")||"0"),r=parseFloat(e.getAttribute("data-duration")||"Infinity"),n=e.tagName==="VIDEO"?"video":"audio",d=this._createParentMedia(s,n,i,r);d&&this._audioOwner==="parent"&&(this._mirrorParentMediaTime(this._currentTime,{force:!0}),!this._paused&&d.el.src&&d.el.play().catch(f=>this._reportPlaybackError(f)))}_observeDynamicMedia(e){if(this._teardownMediaObserver(),typeof MutationObserver>"u"||!e.body)return;const t=new MutationObserver(i=>{var r,n,d,_;for(const f of i){for(const a of f.addedNodes){if(!(a instanceof Element))continue;const l=[];(r=a.matches)!=null&&r.call(a,"audio[data-start], video[data-start]")&&l.push(a);const u=(n=a.querySelectorAll)==null?void 0:n.call(a,"audio[data-start], video[data-start]");if(u)for(const m of u)l.push(m);for(const m of l)this._adoptIframeMedia(m)}for(const a of f.removedNodes){if(!(a instanceof Element))continue;const l=[];(d=a.matches)!=null&&d.call(a,"audio[data-start], video[data-start]")&&l.push(a);const u=(_=a.querySelectorAll)==null?void 0:_.call(a,"audio[data-start], video[data-start]");if(u)for(const m of u)l.push(m);for(const m of l)this._detachIframeMedia(m)}}}),s=e.querySelectorAll("[data-composition-id]");if(s.length>0)for(const i of s)t.observe(i,{childList:!0,subtree:!0});else t.observe(e.body,{childList:!0,subtree:!0});this._mediaObserver=t}_teardownMediaObserver(){var e;(e=this._mediaObserver)==null||e.disconnect(),this._mediaObserver=void 0}_detachIframeMedia(e){var n;const t=e.getAttribute("src")||((n=e.querySelector("source"))==null?void 0:n.getAttribute("src"));if(!t)return;const s=new URL(t,e.ownerDocument.baseURI).href,i=this._parentMedia.findIndex(d=>d.el.src===s);if(i===-1)return;const r=this._parentMedia[i];r.el.pause(),r.el.src="",this._parentMedia.splice(i,1)}_hidePoster(){var e;(e=this.posterEl)==null||e.remove(),this.posterEl=null}};p(w,"MIRROR_DRIFT_THRESHOLD_SECONDS",.05),p(w,"MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES",2);let x=w;customElements.get("hyperframes-player")||customElements.define("hyperframes-player",x);export{x as HyperframesPlayer,j as SPEED_PRESETS,k as formatSpeed,R as formatTime};
|