@hyperframes/studio-server 0.8.200 → 0.8.201
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/{chunk-URMIRXBS.js → chunk-5UEDEOD2.js} +106 -25
- package/dist/{chunk-XMNHDWQI.js → chunk-C2RFW2FX.js} +45 -15
- package/dist/{chunk-ZCCJNXS7.js → chunk-HZCA2O76.js} +14 -12
- package/dist/{chunk-VKQTZKGA.js → chunk-OSZE5WT3.js} +56 -4
- package/dist/helpers/mediaCodecMap.d.ts +19 -9
- package/dist/helpers/mediaCodecMap.js +3 -1
- package/dist/helpers/mediaProxyPreview.js +3 -3
- package/dist/helpers/proxyTranscoder.d.ts +1 -1
- package/dist/helpers/proxyTranscoder.js +2 -2
- package/dist/history/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +45 -13
- package/package.json +3 -3
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
PROXY_VARIANT_CONFIG,
|
|
3
3
|
probeFirstFrameColour,
|
|
4
4
|
probeMediaMetadata
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-C2RFW2FX.js";
|
|
6
6
|
import {
|
|
7
7
|
mkdirWithinProject,
|
|
8
8
|
realProjectRoot,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
utimesSync,
|
|
23
23
|
writeFileSync
|
|
24
24
|
} from "fs";
|
|
25
|
-
import { availableParallelism, tmpdir } from "os";
|
|
25
|
+
import { availableParallelism, tmpdir, totalmem } from "os";
|
|
26
26
|
import { basename, dirname, extname as extname2, isAbsolute, join as join2, relative, resolve, sep } from "path";
|
|
27
27
|
import {
|
|
28
28
|
formatPreviewProxyBox,
|
|
@@ -144,7 +144,7 @@ function cleanupProxyCache(cacheDir, options = {}) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// src/helpers/proxyTranscoder.ts
|
|
147
|
-
var PROXY_PARAMS_VERSION = "
|
|
147
|
+
var PROXY_PARAMS_VERSION = "v7";
|
|
148
148
|
var CACHE_DIR_NAME = ".transcode-cache";
|
|
149
149
|
function boundedEnvInteger(name, fallback, min, max) {
|
|
150
150
|
const raw = process.env[name]?.trim();
|
|
@@ -420,28 +420,91 @@ function chunkRanges(durationSeconds) {
|
|
|
420
420
|
lengthSeconds: index === count - 1 ? durationSeconds - index * PROXY_CHUNK_SECONDS : PROXY_CHUNK_SECONDS
|
|
421
421
|
}));
|
|
422
422
|
}
|
|
423
|
-
|
|
423
|
+
var UNGATED = { size: 1, acquire: async () => {
|
|
424
|
+
}, release: () => {
|
|
425
|
+
} };
|
|
426
|
+
function createPieceGate(size) {
|
|
427
|
+
let free = size;
|
|
428
|
+
const waiters = [];
|
|
429
|
+
return {
|
|
430
|
+
size,
|
|
431
|
+
acquire: (signal) => {
|
|
432
|
+
signal.throwIfAborted();
|
|
433
|
+
if (free > 0) {
|
|
434
|
+
free--;
|
|
435
|
+
return Promise.resolve();
|
|
436
|
+
}
|
|
437
|
+
return new Promise((resolve2, reject) => {
|
|
438
|
+
const grant = () => {
|
|
439
|
+
signal.removeEventListener("abort", onAbort);
|
|
440
|
+
resolve2();
|
|
441
|
+
};
|
|
442
|
+
const onAbort = () => {
|
|
443
|
+
waiters.splice(waiters.indexOf(grant), 1);
|
|
444
|
+
reject(signal.reason);
|
|
445
|
+
};
|
|
446
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
447
|
+
waiters.push(grant);
|
|
448
|
+
});
|
|
449
|
+
},
|
|
450
|
+
release: () => {
|
|
451
|
+
const next = waiters.shift();
|
|
452
|
+
if (next) next();
|
|
453
|
+
else free++;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, signal, gate, onDone = () => {
|
|
424
458
|
}) {
|
|
425
459
|
const extension = extname2(tempPath);
|
|
426
460
|
const pieces = ranges.map((_, index) => `${chunkPrefix}${index}${extension}`);
|
|
461
|
+
const pending = ranges.flatMap((_, index) => existsSync2(pieces[index]) ? [] : [index]);
|
|
427
462
|
let doneSeconds = 0;
|
|
428
463
|
for (const [index, range] of ranges.entries()) {
|
|
429
|
-
if (
|
|
464
|
+
if (!pending.includes(index)) doneSeconds += range.lengthSeconds;
|
|
430
465
|
}
|
|
431
466
|
if (doneSeconds) onDone(doneSeconds);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
467
|
+
const inFlight2 = /* @__PURE__ */ new Map();
|
|
468
|
+
const stop = new AbortController();
|
|
469
|
+
const pieceSignal = AbortSignal.any([signal, stop.signal]);
|
|
470
|
+
let failure;
|
|
471
|
+
let cursor = 0;
|
|
472
|
+
const work = async () => {
|
|
473
|
+
for (; ; ) {
|
|
474
|
+
await gate.acquire(pieceSignal);
|
|
475
|
+
try {
|
|
476
|
+
if (cursor >= pending.length) return;
|
|
477
|
+
const index = pending[cursor++];
|
|
478
|
+
const range = ranges[index];
|
|
479
|
+
const piece = pieces[index];
|
|
480
|
+
const pieceTemp = join2(dirname(piece), `.tmp-${randomUUID()}-${basename(piece)}`);
|
|
481
|
+
try {
|
|
482
|
+
await encode(pieceTemp, range, pieceSignal, (encodedSeconds) => {
|
|
483
|
+
inFlight2.set(index, encodedSeconds);
|
|
484
|
+
onDone(doneSeconds + [...inFlight2.values()].reduce((sum, seconds) => sum + seconds, 0));
|
|
485
|
+
});
|
|
486
|
+
renameSync(pieceTemp, piece);
|
|
487
|
+
inFlight2.delete(index);
|
|
488
|
+
doneSeconds += range.lengthSeconds;
|
|
489
|
+
onDone(doneSeconds);
|
|
490
|
+
} finally {
|
|
491
|
+
if (existsSync2(pieceTemp)) unlinkSync2(pieceTemp);
|
|
492
|
+
}
|
|
493
|
+
} finally {
|
|
494
|
+
gate.release();
|
|
495
|
+
}
|
|
443
496
|
}
|
|
444
|
-
}
|
|
497
|
+
};
|
|
498
|
+
await Promise.all(
|
|
499
|
+
Array.from(
|
|
500
|
+
{ length: Math.min(gate.size, pending.length) },
|
|
501
|
+
() => work().catch((error) => {
|
|
502
|
+
failure ??= error;
|
|
503
|
+
stop.abort();
|
|
504
|
+
})
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
if (failure !== void 0) throw failure;
|
|
445
508
|
const listDir = mkdtempSync(join2(tmpdir(), "hf-proxy-join-"));
|
|
446
509
|
try {
|
|
447
510
|
const listPath = join2(listDir, "pieces.txt");
|
|
@@ -456,6 +519,7 @@ async function encodeInChunks(ranges, chunkPrefix, tempPath, encode, concat, onD
|
|
|
456
519
|
}
|
|
457
520
|
for (const piece of pieces) if (existsSync2(piece)) unlinkSync2(piece);
|
|
458
521
|
}
|
|
522
|
+
var KEYFRAME_EVERY_SECOND_ARGS = ["-force_key_frames", "expr:gte(t,n_forced*1)"];
|
|
459
523
|
var H264_ARGS = [
|
|
460
524
|
"-c:v",
|
|
461
525
|
"libx264",
|
|
@@ -478,7 +542,11 @@ var H264_ARGS = [
|
|
|
478
542
|
"-movflags",
|
|
479
543
|
"+faststart"
|
|
480
544
|
];
|
|
481
|
-
var
|
|
545
|
+
var CORES = availableParallelism();
|
|
546
|
+
var PIECE_MEMORY_BYTES = 3 * 1024 ** 3;
|
|
547
|
+
var ALPHA_PIECE_GATE = createPieceGate(
|
|
548
|
+
Math.max(1, Math.min(4, CORES - 1, Math.floor(totalmem() / PIECE_MEMORY_BYTES)))
|
|
549
|
+
);
|
|
482
550
|
var VP8_ARGS = [
|
|
483
551
|
"-c:v",
|
|
484
552
|
"libvpx",
|
|
@@ -498,8 +566,6 @@ var VP8_ARGS = [
|
|
|
498
566
|
"bt709",
|
|
499
567
|
"-cpu-used",
|
|
500
568
|
"4",
|
|
501
|
-
"-threads",
|
|
502
|
-
VP8_THREADS,
|
|
503
569
|
"-auto-alt-ref",
|
|
504
570
|
"0",
|
|
505
571
|
"-metadata:s:v:0",
|
|
@@ -533,7 +599,12 @@ async function runFfmpeg(sourcePath, outputPath, chunkPrefix, variant, box, sign
|
|
|
533
599
|
const alphaDecoder = keepsAlpha ? alphaDecoderFor(metadata.color.codecName) : null;
|
|
534
600
|
const decoderArgs = alphaDecoder ? ["-c:v", alphaDecoder] : [];
|
|
535
601
|
const totalSeconds = metadata.durationSeconds;
|
|
536
|
-
const
|
|
602
|
+
const gate = keepsAlpha ? ALPHA_PIECE_GATE : UNGATED;
|
|
603
|
+
const vp8Threads = (pieces) => [
|
|
604
|
+
"-threads",
|
|
605
|
+
String(Math.min(4, Math.floor(CORES / pieces)))
|
|
606
|
+
];
|
|
607
|
+
const encode = (target, range, pieceSignal, onEncoded) => spawnFfmpeg(
|
|
537
608
|
ffmpegPath,
|
|
538
609
|
[
|
|
539
610
|
"-y",
|
|
@@ -547,21 +618,31 @@ async function runFfmpeg(sourcePath, outputPath, chunkPrefix, variant, box, sign
|
|
|
547
618
|
...range ? ["-t", String(range.lengthSeconds)] : [],
|
|
548
619
|
"-vf",
|
|
549
620
|
videoFilter,
|
|
550
|
-
...
|
|
621
|
+
...KEYFRAME_EVERY_SECOND_ARGS,
|
|
622
|
+
...keepsAlpha ? [...VP8_ARGS, ...vp8Threads(range ? gate.size : 1)] : H264_ARGS,
|
|
551
623
|
target
|
|
552
624
|
],
|
|
553
625
|
TRANSCODE_STALL_MS,
|
|
554
|
-
|
|
555
|
-
|
|
626
|
+
pieceSignal,
|
|
627
|
+
onEncoded
|
|
556
628
|
);
|
|
557
629
|
const ranges = chunkRanges(totalSeconds);
|
|
558
|
-
if (!ranges)
|
|
630
|
+
if (!ranges) {
|
|
631
|
+
return encode(
|
|
632
|
+
outputPath,
|
|
633
|
+
void 0,
|
|
634
|
+
signal,
|
|
635
|
+
(encodedSeconds) => onProgress({ doneSeconds: encodedSeconds, totalSeconds })
|
|
636
|
+
);
|
|
637
|
+
}
|
|
559
638
|
await encodeInChunks(
|
|
560
639
|
ranges,
|
|
561
640
|
chunkPrefix,
|
|
562
641
|
outputPath,
|
|
563
642
|
encode,
|
|
564
643
|
(listPath, target) => spawnFfmpeg(ffmpegPath, concatArgs(listPath, target, variant), CONCAT_TIMEOUT_MS, signal),
|
|
644
|
+
signal,
|
|
645
|
+
gate,
|
|
565
646
|
(doneSeconds) => onProgress({ doneSeconds, totalSeconds })
|
|
566
647
|
);
|
|
567
648
|
}
|
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
isUnresolvedAssetPlaceholder,
|
|
14
14
|
resolveExistingLocalAsset as resolveAsset
|
|
15
15
|
} from "@hyperframes/parsers/asset-resolution";
|
|
16
|
+
import {
|
|
17
|
+
isHeavyForPreview
|
|
18
|
+
} from "@hyperframes/core";
|
|
16
19
|
|
|
17
20
|
// src/helpers/mediaMetadata.ts
|
|
18
21
|
import { execFile } from "child_process";
|
|
@@ -106,6 +109,22 @@ async function probeFirstFrameColour(filePath, runner = execFileRunner) {
|
|
|
106
109
|
});
|
|
107
110
|
return result.status === 0 ? parseFirstFrameColour(String(result.stderr)) : {};
|
|
108
111
|
}
|
|
112
|
+
function bitDepthOf(stream) {
|
|
113
|
+
const raw = Number(stream.bits_per_raw_sample);
|
|
114
|
+
if (Number.isFinite(raw) && raw > 0) return raw;
|
|
115
|
+
const suffixed = /(\d{1,3})(?:le|be)$/.exec(stream.pix_fmt ?? "");
|
|
116
|
+
return suffixed ? Number(suffixed[1]) : 8;
|
|
117
|
+
}
|
|
118
|
+
function sourceFactsOf(stream, formatBitrate) {
|
|
119
|
+
if (!stream?.width || !stream.height) return void 0;
|
|
120
|
+
const bitrate = Number(stream.bit_rate ?? formatBitrate);
|
|
121
|
+
return {
|
|
122
|
+
width: stream.width,
|
|
123
|
+
height: stream.height,
|
|
124
|
+
bitDepth: bitDepthOf(stream),
|
|
125
|
+
bitrate: Number.isFinite(bitrate) ? bitrate : 0
|
|
126
|
+
};
|
|
127
|
+
}
|
|
109
128
|
async function probeMediaMetadata(filePath, runner = execFileRunner) {
|
|
110
129
|
const kind = inferKindFromPath(filePath);
|
|
111
130
|
if (kind === "audio" || kind === "unknown") {
|
|
@@ -121,7 +140,7 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
|
|
|
121
140
|
"-v",
|
|
122
141
|
"error",
|
|
123
142
|
"-show_entries",
|
|
124
|
-
"stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_disposition=attached_pic:stream_tags=alpha_mode:format=duration",
|
|
143
|
+
"stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample,width,height,bit_rate:stream_disposition=attached_pic:stream_tags=alpha_mode:format=duration,bit_rate",
|
|
125
144
|
"-of",
|
|
126
145
|
"json",
|
|
127
146
|
"--",
|
|
@@ -147,6 +166,7 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
|
|
|
147
166
|
metadata.hasAlpha = stream !== void 0 && videoStreamHasAlpha(stream);
|
|
148
167
|
const duration = Number(parsed.format?.duration);
|
|
149
168
|
if (Number.isFinite(duration) && duration > 0) metadata.durationSeconds = duration;
|
|
169
|
+
metadata.source = sourceFactsOf(stream, parsed.format?.bit_rate);
|
|
150
170
|
}
|
|
151
171
|
return metadata;
|
|
152
172
|
} catch {
|
|
@@ -219,18 +239,22 @@ function resolveProxyVariantRequest(request, facts) {
|
|
|
219
239
|
const expected = proxyVariantFor(facts);
|
|
220
240
|
return request === "auto" || request === expected ? expected : null;
|
|
221
241
|
}
|
|
222
|
-
function decideMediaProxyEligibility(facts) {
|
|
242
|
+
function decideMediaProxyEligibility(facts, lightBox) {
|
|
223
243
|
if (!facts) return { eligible: false, reason: "unknown_codec" };
|
|
224
|
-
if (
|
|
225
|
-
|
|
244
|
+
if (facts.browserHostile) return { eligible: true };
|
|
245
|
+
if (lightBox && facts.source && isHeavyForPreview(facts.source, lightBox)) {
|
|
246
|
+
return { eligible: true };
|
|
247
|
+
}
|
|
248
|
+
return { eligible: false, reason: "browser_safe_codec" };
|
|
226
249
|
}
|
|
227
|
-
function codecFactsFor(codecName, hasAlpha) {
|
|
250
|
+
function codecFactsFor(codecName, hasAlpha, source) {
|
|
228
251
|
const hostile = hostileCodecEntry(codecName);
|
|
229
252
|
return {
|
|
230
253
|
codecName,
|
|
231
254
|
browserHostile: hostile !== void 0,
|
|
232
255
|
representativeMime: hostile?.representativeMime ?? null,
|
|
233
|
-
hasAlpha
|
|
256
|
+
hasAlpha,
|
|
257
|
+
...source && { source }
|
|
234
258
|
};
|
|
235
259
|
}
|
|
236
260
|
async function probeCodecFacts(filePath, runner) {
|
|
@@ -239,7 +263,7 @@ async function probeCodecFacts(filePath, runner) {
|
|
|
239
263
|
if (metadata.kind !== "video") return null;
|
|
240
264
|
const codecName = metadata.color.codecName;
|
|
241
265
|
if (!codecName) return null;
|
|
242
|
-
return codecFactsFor(codecName, metadata.hasAlpha === true);
|
|
266
|
+
return codecFactsFor(codecName, metadata.hasAlpha === true, metadata.source);
|
|
243
267
|
}
|
|
244
268
|
function createMediaCodecProbeCache() {
|
|
245
269
|
return /* @__PURE__ */ new Map();
|
|
@@ -301,9 +325,8 @@ function collectLocalVideoAssets(projectDir, htmlSources) {
|
|
|
301
325
|
return candidates;
|
|
302
326
|
}
|
|
303
327
|
var PROBE_CONCURRENCY = 8;
|
|
304
|
-
async function
|
|
328
|
+
async function probeProjectAssets(projectDir, htmlSources, options) {
|
|
305
329
|
const candidates = collectLocalVideoAssets(projectDir, htmlSources);
|
|
306
|
-
if (candidates.size === 0) return {};
|
|
307
330
|
const cache = options.cache ?? defaultProbeCache;
|
|
308
331
|
const entries = [...candidates.entries()];
|
|
309
332
|
const facts = new Array(entries.length).fill(null);
|
|
@@ -319,12 +342,18 @@ async function scanProjectMediaCodecMap(projectDir, htmlSources, options = {}) {
|
|
|
319
342
|
}
|
|
320
343
|
})
|
|
321
344
|
);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
345
|
+
return entries.map(([, pathname], index) => [pathname, facts[index] ?? null]);
|
|
346
|
+
}
|
|
347
|
+
async function scanProjectMedia(projectDir, htmlSources, options = {}) {
|
|
348
|
+
const scan = { codecMap: {}, sourceFacts: {} };
|
|
349
|
+
for (const [pathname, facts] of await probeProjectAssets(projectDir, htmlSources, options)) {
|
|
350
|
+
if (facts?.browserHostile) scan.codecMap[pathname] = facts;
|
|
351
|
+
if (facts?.source) scan.sourceFacts[pathname] = facts.source;
|
|
352
|
+
}
|
|
353
|
+
return scan;
|
|
354
|
+
}
|
|
355
|
+
async function scanProjectMediaCodecMap(projectDir, htmlSources, options = {}) {
|
|
356
|
+
return (await scanProjectMedia(projectDir, htmlSources, options)).codecMap;
|
|
328
357
|
}
|
|
329
358
|
|
|
330
359
|
export {
|
|
@@ -343,5 +372,6 @@ export {
|
|
|
343
372
|
decideMediaProxyEligibility,
|
|
344
373
|
createMediaCodecProbeCache,
|
|
345
374
|
probeAssetCodec,
|
|
375
|
+
scanProjectMedia,
|
|
346
376
|
scanProjectMediaCodecMap
|
|
347
377
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROXY_PARAMS_VERSION,
|
|
3
3
|
resolveProxy
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5UEDEOD2.js";
|
|
5
5
|
import {
|
|
6
6
|
createMediaCodecProbeCache,
|
|
7
7
|
proxyVariantFor,
|
|
8
8
|
recordProxyPrewarm,
|
|
9
|
-
|
|
9
|
+
scanProjectMedia,
|
|
10
10
|
shouldPrewarmProxy
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-C2RFW2FX.js";
|
|
12
12
|
|
|
13
13
|
// src/helpers/mediaProxyPreview.ts
|
|
14
14
|
import { resolve } from "path";
|
|
@@ -25,17 +25,15 @@ function proxyEtagSalt(raw, box) {
|
|
|
25
25
|
return `:proxy:${raw}:${PROXY_PARAMS_VERSION}${box ? `:${formatPreviewProxyBox(box)}` : ""}`;
|
|
26
26
|
}
|
|
27
27
|
async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeCache, prewarm = true, signal, box) {
|
|
28
|
-
let
|
|
28
|
+
let scan;
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
projectDir,
|
|
32
|
-
htmlSources,
|
|
33
|
-
probeCache ? { cache: probeCache } : {}
|
|
34
|
-
);
|
|
30
|
+
scan = await scanProjectMedia(projectDir, htmlSources, probeCache ? { cache: probeCache } : {});
|
|
35
31
|
} catch {
|
|
36
32
|
return html;
|
|
37
33
|
}
|
|
38
|
-
|
|
34
|
+
const { codecMap: map, sourceFacts } = scan;
|
|
35
|
+
const hasMap = Object.keys(map).length > 0;
|
|
36
|
+
if (!hasMap && Object.keys(sourceFacts).length === 0) return html;
|
|
39
37
|
for (const [rootRelativePathname, facts] of Object.entries(map)) {
|
|
40
38
|
if (!prewarm || !shouldPrewarmProxy(facts)) continue;
|
|
41
39
|
recordProxyPrewarm();
|
|
@@ -48,8 +46,12 @@ async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeC
|
|
|
48
46
|
).catch(() => {
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
|
-
const
|
|
52
|
-
const
|
|
49
|
+
const safeJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
50
|
+
const assignments = [
|
|
51
|
+
...hasMap ? [`window.__HF_MEDIA_CODEC_MAP__=${safeJson(map)};`] : [],
|
|
52
|
+
`window.__HF_MEDIA_SOURCE_FACTS__=${safeJson(sourceFacts)};`
|
|
53
|
+
];
|
|
54
|
+
const tag = `<script data-hf-media-codec-map>${assignments.join("")}</script>`;
|
|
53
55
|
return insertBeforeCloseTag(html, "head", `${tag}
|
|
54
56
|
`) ?? `${tag}
|
|
55
57
|
${html}`;
|
|
@@ -106,6 +106,7 @@ import { isAbsolute, relative, resolve, sep } from "path";
|
|
|
106
106
|
import { replaceFileAtomically } from "@hyperframes/core/atomic-file";
|
|
107
107
|
|
|
108
108
|
// src/history/ownerLock.ts
|
|
109
|
+
import { spawnSync } from "child_process";
|
|
109
110
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
110
111
|
import {
|
|
111
112
|
linkSync,
|
|
@@ -116,6 +117,7 @@ import {
|
|
|
116
117
|
statSync,
|
|
117
118
|
writeFileSync
|
|
118
119
|
} from "fs";
|
|
120
|
+
import { uptime } from "os";
|
|
119
121
|
import { join } from "path";
|
|
120
122
|
var HistoryBusyError = class extends Error {
|
|
121
123
|
constructor(pid, url) {
|
|
@@ -130,6 +132,7 @@ var HistoryBusyError = class extends Error {
|
|
|
130
132
|
var WINDOWS_BUSY = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
131
133
|
var NO_HARD_LINKS = /* @__PURE__ */ new Set(["ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EPERM"]);
|
|
132
134
|
var TAKING_MS = 2e3;
|
|
135
|
+
var BOOT_SLACK_MS = 6e4;
|
|
133
136
|
function busyOnWindows(error) {
|
|
134
137
|
return process.platform === "win32" && WINDOWS_BUSY.has(error.code ?? "");
|
|
135
138
|
}
|
|
@@ -142,6 +145,48 @@ function alive(pid) {
|
|
|
142
145
|
return error.code === "EPERM";
|
|
143
146
|
}
|
|
144
147
|
}
|
|
148
|
+
function processStart(pid) {
|
|
149
|
+
try {
|
|
150
|
+
if (process.platform === "linux") {
|
|
151
|
+
const stat4 = readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
152
|
+
return stat4.slice(stat4.lastIndexOf(")") + 2).split(" ")[19] ?? null;
|
|
153
|
+
}
|
|
154
|
+
if (process.platform === "win32") return null;
|
|
155
|
+
const ps = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
156
|
+
encoding: "utf-8",
|
|
157
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
158
|
+
});
|
|
159
|
+
return ps.status === 0 ? ps.stdout.trim() || null : null;
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function recordedStart(file, pid) {
|
|
165
|
+
try {
|
|
166
|
+
const [owner, ...start] = readFileSync(`${file}.start`, "utf-8").split("\n");
|
|
167
|
+
return Number(owner) === pid && start.length > 0 ? start.join("\n") : null;
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function recordStart(file) {
|
|
173
|
+
const start = processStart(process.pid);
|
|
174
|
+
if (start) writeFileSync(`${file}.start`, `${process.pid}
|
|
175
|
+
${start}`);
|
|
176
|
+
}
|
|
177
|
+
function holdsLock(file, pid) {
|
|
178
|
+
if (!alive(pid)) return false;
|
|
179
|
+
const recorded = recordedStart(file, pid);
|
|
180
|
+
if (recorded !== null) {
|
|
181
|
+
const now = processStart(pid);
|
|
182
|
+
return now === null || now === recorded;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return statSync(file).mtimeMs > Date.now() - uptime() * 1e3 - BOOT_SLACK_MS;
|
|
186
|
+
} catch {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
145
190
|
function ownerOf(file) {
|
|
146
191
|
try {
|
|
147
192
|
const text = readFileSync(file, "utf-8");
|
|
@@ -160,13 +205,14 @@ function beingTaken(file) {
|
|
|
160
205
|
}
|
|
161
206
|
}
|
|
162
207
|
function deadHolder(file, pid) {
|
|
163
|
-
return !
|
|
208
|
+
return !holdsLock(file, pid) && !beingTaken(file);
|
|
164
209
|
}
|
|
165
210
|
function claim(file) {
|
|
166
211
|
const draft = `${file}-${process.pid}-${randomUUID2()}.tmp`;
|
|
167
212
|
writeFileSync(draft, String(process.pid));
|
|
168
213
|
try {
|
|
169
214
|
linkSync(draft, file);
|
|
215
|
+
recordStart(file);
|
|
170
216
|
return true;
|
|
171
217
|
} catch (error) {
|
|
172
218
|
const code = error.code ?? "";
|
|
@@ -180,6 +226,7 @@ function claim(file) {
|
|
|
180
226
|
function createExclusively(file) {
|
|
181
227
|
try {
|
|
182
228
|
writeFileSync(file, String(process.pid), { flag: "wx" });
|
|
229
|
+
recordStart(file);
|
|
183
230
|
return true;
|
|
184
231
|
} catch (error) {
|
|
185
232
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -187,7 +234,9 @@ function createExclusively(file) {
|
|
|
187
234
|
}
|
|
188
235
|
}
|
|
189
236
|
function releaseOwn(file) {
|
|
190
|
-
if (ownerOf(file)
|
|
237
|
+
if (ownerOf(file) !== process.pid) return;
|
|
238
|
+
rmSync(file, { force: true });
|
|
239
|
+
rmSync(`${file}.start`, { force: true });
|
|
191
240
|
}
|
|
192
241
|
function evictDeadOwner(file) {
|
|
193
242
|
const evictor = `${file}.evict`;
|
|
@@ -198,7 +247,10 @@ function evictDeadOwner(file) {
|
|
|
198
247
|
}
|
|
199
248
|
try {
|
|
200
249
|
const pid = ownerOf(file);
|
|
201
|
-
if (pid !== null && deadHolder(file, pid))
|
|
250
|
+
if (pid !== null && deadHolder(file, pid)) {
|
|
251
|
+
rmSync(file, { force: true });
|
|
252
|
+
rmSync(`${file}.start`, { force: true });
|
|
253
|
+
}
|
|
202
254
|
return true;
|
|
203
255
|
} finally {
|
|
204
256
|
releaseOwn(evictor);
|
|
@@ -213,7 +265,7 @@ function advertise(urlFile, url) {
|
|
|
213
265
|
function servedBy(file, urlFile, pid) {
|
|
214
266
|
try {
|
|
215
267
|
const said = JSON.parse(readFileSync(urlFile, "utf-8"));
|
|
216
|
-
const live = said.pid === pid &&
|
|
268
|
+
const live = said.pid === pid && holdsLock(file, pid) && ownerOf(file) === pid;
|
|
217
269
|
return live && typeof said.url === "string" ? said.url : void 0;
|
|
218
270
|
} catch {
|
|
219
271
|
return void 0;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PreviewSourceFacts, PreviewProxyBox } from '@hyperframes/core';
|
|
2
|
+
|
|
1
3
|
interface FfprobeRunResult {
|
|
2
4
|
status: number | null;
|
|
3
5
|
stdout: string | Buffer;
|
|
@@ -31,6 +33,8 @@ interface AssetCodecFacts {
|
|
|
31
33
|
/** Source carries an alpha channel (`MediaMetadata.hasAlpha`). Alpha sources use a
|
|
32
34
|
* VP8/WebM proxy so their transparency is preserved across Chromium builds. */
|
|
33
35
|
hasAlpha: boolean;
|
|
36
|
+
/** Size, bit depth and bitrate, for deciding whether a playable file deserves a lighter copy. */
|
|
37
|
+
source?: PreviewSourceFacts;
|
|
34
38
|
}
|
|
35
39
|
/** Server-root-relative URL pathname -> that asset's codec facts. */
|
|
36
40
|
type MediaCodecMap = Record<string, AssetCodecFacts>;
|
|
@@ -96,8 +100,11 @@ type MediaProxyEligibility = {
|
|
|
96
100
|
eligible: false;
|
|
97
101
|
reason: MediaProxyIneligibilityReason;
|
|
98
102
|
};
|
|
99
|
-
/**
|
|
100
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Single policy gate shared by proactive scans and on-demand proxy routes. A file Chrome can play is
|
|
105
|
+
* eligible only for a lighter copy (`lightBox`, the box that request asks for) and only when it is heavy there.
|
|
106
|
+
*/
|
|
107
|
+
declare function decideMediaProxyEligibility(facts: AssetCodecFacts | null, lightBox?: PreviewProxyBox): MediaProxyEligibility;
|
|
101
108
|
interface CachedAssetProbe {
|
|
102
109
|
/** The file a symlinked path pointed at, so retargeting the link is a miss. */
|
|
103
110
|
target: string;
|
|
@@ -126,12 +133,15 @@ interface ScanProjectMediaCodecMapOptions {
|
|
|
126
133
|
cache?: MediaCodecProbeCache;
|
|
127
134
|
runner?: FfprobeRunner;
|
|
128
135
|
}
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
/** What a scan of a project's local videos finds: the hostile ones for the proxy map, and every readable one's size. */
|
|
137
|
+
interface ProjectMediaScan {
|
|
138
|
+
codecMap: MediaCodecMap;
|
|
139
|
+
/** Root-relative URL pathname -> size, bit depth and bitrate. */
|
|
140
|
+
sourceFacts: Record<string, PreviewSourceFacts>;
|
|
141
|
+
}
|
|
142
|
+
/** Scans composition HTML for local `<video src>` references; a video that can't be probed is omitted. */
|
|
143
|
+
declare function scanProjectMedia(projectDir: string, htmlSources: HtmlSourceLike[], options?: ScanProjectMediaCodecMapOptions): Promise<ProjectMediaScan>;
|
|
144
|
+
/** The injection map alone: root-relative URL pathname -> codec facts of the hostile videos. */
|
|
135
145
|
declare function scanProjectMediaCodecMap(projectDir: string, htmlSources: HtmlSourceLike[], options?: ScanProjectMediaCodecMapOptions): Promise<MediaCodecMap>;
|
|
136
146
|
|
|
137
|
-
export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type BrowserHostileCodec, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_VARIANT_CONFIG, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, mediaProxyDemand, probeAssetCodec, proxyVariantFor, recordProxyPrewarm, recordProxyRequest, resolveProxyVariantRequest, scanProjectMediaCodecMap, shouldPrewarmProxy };
|
|
147
|
+
export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type BrowserHostileCodec, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_VARIANT_CONFIG, type ProjectMediaScan, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, mediaProxyDemand, probeAssetCodec, proxyVariantFor, recordProxyPrewarm, recordProxyRequest, resolveProxyVariantRequest, scanProjectMedia, scanProjectMediaCodecMap, shouldPrewarmProxy };
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
recordProxyPrewarm,
|
|
12
12
|
recordProxyRequest,
|
|
13
13
|
resolveProxyVariantRequest,
|
|
14
|
+
scanProjectMedia,
|
|
14
15
|
scanProjectMediaCodecMap,
|
|
15
16
|
shouldPrewarmProxy
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-C2RFW2FX.js";
|
|
17
18
|
import "../chunk-QF7MFJJU.js";
|
|
18
19
|
import "../chunk-SVQFE44C.js";
|
|
19
20
|
import "../chunk-L3HJVTQE.js";
|
|
@@ -30,6 +31,7 @@ export {
|
|
|
30
31
|
recordProxyPrewarm,
|
|
31
32
|
recordProxyRequest,
|
|
32
33
|
resolveProxyVariantRequest,
|
|
34
|
+
scanProjectMedia,
|
|
33
35
|
scanProjectMediaCodecMap,
|
|
34
36
|
shouldPrewarmProxy
|
|
35
37
|
};
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
isAutoProxyEnabled,
|
|
5
5
|
proxyEtagSalt,
|
|
6
6
|
resolvePreviewMediaCodecProbeCache
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-HZCA2O76.js";
|
|
8
|
+
import "../chunk-5UEDEOD2.js";
|
|
9
|
+
import "../chunk-C2RFW2FX.js";
|
|
10
10
|
import "../chunk-QF7MFJJU.js";
|
|
11
11
|
import "../chunk-SVQFE44C.js";
|
|
12
12
|
import "../chunk-L3HJVTQE.js";
|
|
@@ -11,7 +11,7 @@ import { ProxyVariant } from './mediaCodecMap.js';
|
|
|
11
11
|
* caller's `signal` detaches that caller; once no caller is left, the copy is
|
|
12
12
|
* dropped from the queue or its ffmpeg is killed.
|
|
13
13
|
*/
|
|
14
|
-
declare const PROXY_PARAMS_VERSION = "
|
|
14
|
+
declare const PROXY_PARAMS_VERSION = "v7";
|
|
15
15
|
declare const CACHE_DIR_NAME = ".transcode-cache";
|
|
16
16
|
/** A copy that makes no progress this long is ended: a hung encode, not a slow one. */
|
|
17
17
|
declare const TRANSCODE_STALL_MS: number;
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
resolveProxies,
|
|
18
18
|
resolveProxy,
|
|
19
19
|
waitForProxy
|
|
20
|
-
} from "../chunk-
|
|
21
|
-
import "../chunk-
|
|
20
|
+
} from "../chunk-5UEDEOD2.js";
|
|
21
|
+
import "../chunk-C2RFW2FX.js";
|
|
22
22
|
import "../chunk-QF7MFJJU.js";
|
|
23
23
|
import "../chunk-SVQFE44C.js";
|
|
24
24
|
import "../chunk-L3HJVTQE.js";
|
package/dist/history/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ interface DuplicateElementResult {
|
|
|
93
93
|
matched: boolean;
|
|
94
94
|
newId: string | null;
|
|
95
95
|
}
|
|
96
|
-
declare function duplicateElementInHtml(source: string, target: SourceMutationTarget, newId: string, at: number): DuplicateElementResult;
|
|
96
|
+
declare function duplicateElementInHtml(source: string, target: SourceMutationTarget, newId: string, at: number, fallbackDuration?: number): DuplicateElementResult;
|
|
97
97
|
|
|
98
98
|
interface FileMutationInput {
|
|
99
99
|
sourceFile: string;
|
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
resolveProjectSignature,
|
|
63
63
|
retainSnapshotOutput,
|
|
64
64
|
settledFileTag
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-OSZE5WT3.js";
|
|
66
66
|
import {
|
|
67
67
|
clearElementScreenshotIsolation,
|
|
68
68
|
getElementScreenshotClip
|
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
isAutoProxyEnabled,
|
|
83
83
|
proxyEtagSalt,
|
|
84
84
|
resolvePreviewMediaCodecProbeCache
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-HZCA2O76.js";
|
|
86
86
|
import {
|
|
87
87
|
CACHE_DIR_NAME,
|
|
88
88
|
PROXY_PENDING_RETRY_AFTER_SECONDS,
|
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
proxyActivityMark,
|
|
94
94
|
resolveProxy,
|
|
95
95
|
waitForProxy
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-5UEDEOD2.js";
|
|
97
97
|
import {
|
|
98
98
|
PROXY_VARIANT_CONFIG,
|
|
99
99
|
decideMediaProxyEligibility,
|
|
@@ -102,7 +102,7 @@ import {
|
|
|
102
102
|
probeMediaMetadata,
|
|
103
103
|
recordProxyRequest,
|
|
104
104
|
resolveProxyVariantRequest
|
|
105
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-C2RFW2FX.js";
|
|
106
106
|
import {
|
|
107
107
|
folderGone,
|
|
108
108
|
isInHiddenOrVendorDir,
|
|
@@ -3885,7 +3885,11 @@ import {
|
|
|
3885
3885
|
STUDIO_PREVIEW_MARK_META,
|
|
3886
3886
|
STUDIO_PREVIEW_ERRORS
|
|
3887
3887
|
} from "@hyperframes/core/studio-preview-mark";
|
|
3888
|
-
import {
|
|
3888
|
+
import {
|
|
3889
|
+
PREVIEW_PROXY_BOX_PARAM,
|
|
3890
|
+
PREVIEW_PROXY_LIGHT_PARAM,
|
|
3891
|
+
parsePreviewProxyBox
|
|
3892
|
+
} from "@hyperframes/core";
|
|
3889
3893
|
import {
|
|
3890
3894
|
GSAP_BASE_URL_META,
|
|
3891
3895
|
gsapCdnDist as gsapCdnDist2,
|
|
@@ -4502,7 +4506,8 @@ ${etag}`;
|
|
|
4502
4506
|
const proxyParam = c.req.query("hf-proxy");
|
|
4503
4507
|
const boxParam = c.req.query(PREVIEW_PROXY_BOX_PARAM);
|
|
4504
4508
|
const proxyBox = boxParam === void 0 ? void 0 : parsePreviewProxyBox(boxParam);
|
|
4505
|
-
|
|
4509
|
+
const lightRequested = c.req.query(PREVIEW_PROXY_LIGHT_PARAM) !== void 0;
|
|
4510
|
+
if (proxyBox === null || proxyBox && proxyParam === void 0 || lightRequested && !proxyBox) {
|
|
4506
4511
|
return c.text("not found", 404);
|
|
4507
4512
|
}
|
|
4508
4513
|
let proxyVariant;
|
|
@@ -4511,7 +4516,7 @@ ${etag}`;
|
|
|
4511
4516
|
return c.text("not found", 404);
|
|
4512
4517
|
}
|
|
4513
4518
|
const facts = await probeAssetCodec(file, void 0, mediaCodecProbeCache);
|
|
4514
|
-
const eligibility = decideMediaProxyEligibility(facts);
|
|
4519
|
+
const eligibility = decideMediaProxyEligibility(facts, lightRequested ? proxyBox : void 0);
|
|
4515
4520
|
if (!eligibility.eligible) {
|
|
4516
4521
|
return c.text(`media proxy unavailable: ${eligibility.reason}`, 422);
|
|
4517
4522
|
}
|
|
@@ -6648,9 +6653,29 @@ function registerHistoryRoutes(api, adapter) {
|
|
|
6648
6653
|
);
|
|
6649
6654
|
}
|
|
6650
6655
|
|
|
6656
|
+
// src/helpers/previewErrorPage.ts
|
|
6657
|
+
import { STUDIO_PREVIEW_ERRORS as STUDIO_PREVIEW_ERRORS2 } from "@hyperframes/core/studio-preview-mark";
|
|
6658
|
+
function previewErrorPage(status) {
|
|
6659
|
+
const message = `The preview page could not be loaded (HTTP ${status}).`;
|
|
6660
|
+
return `<!doctype html><meta charset="utf-8"><script>window.${STUDIO_PREVIEW_ERRORS2}=[${JSON.stringify(message)}]</script><p>${message}</p>`;
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6651
6663
|
// src/createStudioApi.ts
|
|
6664
|
+
async function answerRefusedFrameWithPage(c, next) {
|
|
6665
|
+
await next();
|
|
6666
|
+
if (c.req.header("sec-fetch-dest") !== "iframe" || c.res.status < 400) return;
|
|
6667
|
+
const headers = new Headers(c.res.headers);
|
|
6668
|
+
headers.set("content-type", "text/html; charset=utf-8");
|
|
6669
|
+
headers.delete("content-length");
|
|
6670
|
+
c.res = new Response(previewErrorPage(c.res.status), {
|
|
6671
|
+
status: c.res.status,
|
|
6672
|
+
headers
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6652
6675
|
function createStudioApi(adapter) {
|
|
6653
6676
|
const api = new Hono();
|
|
6677
|
+
api.use("/projects/:id/preview/*", answerRefusedFrameWithPage);
|
|
6678
|
+
api.use("/projects/:id/preview", answerRefusedFrameWithPage);
|
|
6654
6679
|
api.use(async function answerProjectDirMissingAfterErrorHandlers(c, next) {
|
|
6655
6680
|
const hostHeaders = new Headers(c.res.headers);
|
|
6656
6681
|
await next();
|
|
@@ -6701,13 +6726,14 @@ function createStudioApi(adapter) {
|
|
|
6701
6726
|
// src/helpers/duplicateElement.ts
|
|
6702
6727
|
import { sameInstant } from "@hyperframes/core/clip-facts";
|
|
6703
6728
|
import { ensureHfIds as ensureHfIds4 } from "@hyperframes/parsers/hf-ids";
|
|
6704
|
-
function duplicateElementInHtml(source, target, newId, at) {
|
|
6729
|
+
function duplicateElementInHtml(source, target, newId, at, fallbackDuration = 0) {
|
|
6705
6730
|
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
6706
6731
|
const element = findTargetElement(document, target);
|
|
6707
6732
|
if (!element || !element.parentElement) return { html: source, matched: false, newId: null };
|
|
6708
|
-
const duration = numericAttribute(element, "data-duration");
|
|
6733
|
+
const duration = element.hasAttribute("data-duration") ? numericAttribute(element, "data-duration") : fallbackDuration;
|
|
6734
|
+
const start = numericAttribute(element, "data-start");
|
|
6709
6735
|
const track = numericAttribute(element, "data-track-index") ?? 0;
|
|
6710
|
-
if (duration === null ||
|
|
6736
|
+
if (duration === null || start === null) {
|
|
6711
6737
|
return { html: source, matched: false, newId: null };
|
|
6712
6738
|
}
|
|
6713
6739
|
const uniqueId = nextUniqueId(document, newId);
|
|
@@ -6717,13 +6743,13 @@ function duplicateElementInHtml(source, target, newId, at) {
|
|
|
6717
6743
|
clone.setAttribute("id", uniqueId);
|
|
6718
6744
|
dedupeClonedCompositionId(document, clone);
|
|
6719
6745
|
stripStableIds(clone);
|
|
6720
|
-
clone
|
|
6746
|
+
moveElementStart(clone, start, at);
|
|
6721
6747
|
element.parentElement.insertBefore(clone, element.nextSibling);
|
|
6722
6748
|
const html = wrappedFragment ? document.body.innerHTML || "" : document.toString();
|
|
6723
6749
|
return { html: ensureHfIds4(html), matched: true, newId: uniqueId };
|
|
6724
6750
|
}
|
|
6725
6751
|
function rippleElements(document, element, at, duration, track) {
|
|
6726
|
-
for (const candidate of Array.from(document.querySelectorAll("[data-start]
|
|
6752
|
+
for (const candidate of Array.from(document.querySelectorAll("[data-start]"))) {
|
|
6727
6753
|
if (candidate === element || candidate.getAttribute("data-track-index") !== String(track)) {
|
|
6728
6754
|
continue;
|
|
6729
6755
|
}
|
|
@@ -6731,9 +6757,15 @@ function rippleElements(document, element, at, duration, track) {
|
|
|
6731
6757
|
if (start === null) continue;
|
|
6732
6758
|
const metInsertionPoint = sameInstant(start, at);
|
|
6733
6759
|
if (start < at && !metInsertionPoint) continue;
|
|
6734
|
-
candidate
|
|
6760
|
+
moveElementStart(candidate, start, (metInsertionPoint ? at : start) + duration);
|
|
6735
6761
|
}
|
|
6736
6762
|
}
|
|
6763
|
+
function moveElementStart(element, start, nextStart) {
|
|
6764
|
+
element.setAttribute("data-start", String(nextStart));
|
|
6765
|
+
if (!element.hasAttribute("data-end")) return;
|
|
6766
|
+
const end = numericAttribute(element, "data-end");
|
|
6767
|
+
if (end !== null) element.setAttribute("data-end", String(end + (nextStart - start)));
|
|
6768
|
+
}
|
|
6737
6769
|
function stripStableIds(element) {
|
|
6738
6770
|
element.removeAttribute("data-hf-id");
|
|
6739
6771
|
for (const child of Array.from(element.querySelectorAll("[data-hf-id]"))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.201",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"linkedom": "^0.18.12",
|
|
85
85
|
"postcss": "^8.5.8",
|
|
86
86
|
"postcss-selector-parser": "^7.1.2",
|
|
87
|
-
"@hyperframes/core": "0.8.
|
|
88
|
-
"@hyperframes/parsers": "0.8.
|
|
87
|
+
"@hyperframes/core": "0.8.201",
|
|
88
|
+
"@hyperframes/parsers": "0.8.201"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@types/node": "^25.0.10",
|