@bendyline/squisq-editor-react 2.4.5 → 2.4.7
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/README.md +8 -1
- package/dist/{chunk-PEI5EBEN.js → chunk-4F7SN6WU.js} +1 -1
- package/dist/{chunk-7OKWPWEQ.js → chunk-55DQFHK5.js} +184 -48
- package/dist/{chunk-V6Z7GG55.js → chunk-IQFPX2CE.js} +1 -1
- package/dist/{chunk-WLJ623UZ.js → chunk-K2WJYVS4.js} +7 -6
- package/dist/{chunk-PDCKJCOS.js → chunk-OWOKKZAU.js} +43 -11
- package/dist/{chunk-WDX6UDL5.js → chunk-YPRX32GY.js} +1 -1
- package/dist/{chunk-PKGBWNUQ.js → chunk-ZSQN6IO7.js} +1061 -141
- package/dist/index.d.ts +7 -9
- package/dist/index.js +21 -7
- package/dist/json-editor/index.js +2 -2
- package/dist/recorder/index.d.ts +2 -2
- package/dist/recorder/index.js +17 -3
- package/dist/{recorder-C0Tkyu5W.d.ts → recorder-CnOKlYY3.d.ts} +96 -202
- package/dist/shell/index.js +5 -5
- package/dist/teleprompter/index.d.ts +2 -2
- package/dist/teleprompter/index.js +2 -2
- package/dist/useNarrationStage-Bk3mdW75.d.ts +560 -0
- package/package.json +4 -4
- package/dist/useNarrationStage-Bqo18PBw.d.ts +0 -313
package/README.md
CHANGED
|
@@ -69,7 +69,14 @@ and block-at-a-time / timeline editing primitives (`useBlockNavigator`,
|
|
|
69
69
|
(`versioningPrunePolicy`, default keep-last-50); a Version History panel
|
|
70
70
|
appears in the toolbar.
|
|
71
71
|
- **Recording** — with a `mediaProvider` wired, a record button appears in the
|
|
72
|
-
toolbar (`allowRecording`, default `true`).
|
|
72
|
+
toolbar (`allowRecording`, default `true`). The recorder's Advanced device
|
|
73
|
+
settings expando discovers microphones/cameras, shows only capture
|
|
74
|
+
constraints advertised by the browser, separates camera and screen
|
|
75
|
+
constraints for dual recordings, and exposes MediaRecorder codec, bitrate,
|
|
76
|
+
and keyframe hints. Preferred constraints use the closest available setting;
|
|
77
|
+
Required microphone/camera constraints fail when the selected input cannot
|
|
78
|
+
satisfy them (screen settings remain preferences, as required by
|
|
79
|
+
`getDisplayMedia`).
|
|
73
80
|
- **Presentation mode** — every Use view can fill the Squisq control, open a
|
|
74
81
|
synchronized audience window, or take over the entire screen. Slideshow/video
|
|
75
82
|
playback, Page/Document scrolling, and the Narrate surface remain linked to
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
markdownToTiptap,
|
|
8
8
|
tiptapToMarkdown
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-K2WJYVS4.js";
|
|
10
10
|
import {
|
|
11
11
|
ImageEditor,
|
|
12
12
|
ImageViewer,
|
|
@@ -15,13 +15,13 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
RecorderPanel,
|
|
17
17
|
useModalDialog
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ZSQN6IO7.js";
|
|
19
19
|
import {
|
|
20
20
|
Icon
|
|
21
21
|
} from "./chunk-GS7QWYFT.js";
|
|
22
22
|
import {
|
|
23
23
|
TeleprompterView
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-IQFPX2CE.js";
|
|
25
25
|
|
|
26
26
|
// src/fileKind.ts
|
|
27
27
|
var EXT_TO_LANGUAGE = {
|
|
@@ -18602,6 +18602,28 @@ function parseSquisqMediaPayload(raw) {
|
|
|
18602
18602
|
}
|
|
18603
18603
|
return null;
|
|
18604
18604
|
}
|
|
18605
|
+
function squisqMediaKind(mimeType) {
|
|
18606
|
+
const normalized = mimeType.toLowerCase();
|
|
18607
|
+
if (normalized.startsWith("image/")) return "image";
|
|
18608
|
+
if (normalized.startsWith("video/")) return "video";
|
|
18609
|
+
if (normalized.startsWith("audio/")) return "audio";
|
|
18610
|
+
return "file";
|
|
18611
|
+
}
|
|
18612
|
+
function escapeHtmlAttribute(value) {
|
|
18613
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
18614
|
+
}
|
|
18615
|
+
function buildSquisqMediaReference(payload) {
|
|
18616
|
+
switch (squisqMediaKind(payload.mimeType)) {
|
|
18617
|
+
case "image":
|
|
18618
|
+
return ``;
|
|
18619
|
+
case "video":
|
|
18620
|
+
return `<video src="${escapeHtmlAttribute(payload.name)}" controls width="480"></video>`;
|
|
18621
|
+
case "audio":
|
|
18622
|
+
return `<audio src="${escapeHtmlAttribute(payload.name)}" controls></audio>`;
|
|
18623
|
+
default:
|
|
18624
|
+
return `[${payload.alt}](${payload.name})`;
|
|
18625
|
+
}
|
|
18626
|
+
}
|
|
18605
18627
|
|
|
18606
18628
|
// src/rawEditorIsolation.ts
|
|
18607
18629
|
var markdownFenceMaskCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -19003,13 +19025,13 @@ function RawEditor({
|
|
|
19003
19025
|
const raw = dt.getData(SQUISQ_MEDIA_MIME);
|
|
19004
19026
|
if (!raw) return;
|
|
19005
19027
|
const payload = parseSquisqMediaPayload(raw);
|
|
19006
|
-
if (!payload
|
|
19028
|
+
if (!payload) return;
|
|
19007
19029
|
e2.preventDefault();
|
|
19008
19030
|
e2.stopPropagation();
|
|
19009
19031
|
const target = editor.getTargetAtClientPoint(e2.clientX, e2.clientY);
|
|
19010
19032
|
const position = target?.position ?? editor.getPosition();
|
|
19011
19033
|
if (!position) return;
|
|
19012
|
-
const markdown =
|
|
19034
|
+
const markdown = buildSquisqMediaReference(payload);
|
|
19013
19035
|
editor.executeEdits("squisq-media-drop", [
|
|
19014
19036
|
{
|
|
19015
19037
|
range: new monaco.Range(
|
|
@@ -27681,17 +27703,26 @@ var TiptapVideo = Node3.create({
|
|
|
27681
27703
|
startAt: {
|
|
27682
27704
|
default: null,
|
|
27683
27705
|
parseHTML: (el) => el.getAttribute("data-squisq-video-start-at"),
|
|
27684
|
-
renderHTML: (attrs) =>
|
|
27706
|
+
renderHTML: (attrs) => {
|
|
27707
|
+
const placement = normalizeVideoPlacement(attrs.placement);
|
|
27708
|
+
return (placement === "content" || !normalizeLockToBlock(attrs.lockToBlock)) && attrs.startAt != null ? { "data-squisq-video-start-at": String(attrs.startAt) } : {};
|
|
27709
|
+
}
|
|
27685
27710
|
},
|
|
27686
27711
|
clipStart: {
|
|
27687
27712
|
default: null,
|
|
27688
27713
|
parseHTML: (el) => el.getAttribute("data-squisq-video-clip-start"),
|
|
27689
|
-
renderHTML: (attrs) =>
|
|
27714
|
+
renderHTML: (attrs) => {
|
|
27715
|
+
const placement = normalizeVideoPlacement(attrs.placement);
|
|
27716
|
+
return (placement === "content" || !normalizeLockToBlock(attrs.lockToBlock)) && attrs.clipStart != null ? { "data-squisq-video-clip-start": String(attrs.clipStart) } : {};
|
|
27717
|
+
}
|
|
27690
27718
|
},
|
|
27691
27719
|
clipEnd: {
|
|
27692
27720
|
default: null,
|
|
27693
27721
|
parseHTML: (el) => el.getAttribute("data-squisq-video-clip-end"),
|
|
27694
|
-
renderHTML: (attrs) =>
|
|
27722
|
+
renderHTML: (attrs) => {
|
|
27723
|
+
const placement = normalizeVideoPlacement(attrs.placement);
|
|
27724
|
+
return (placement === "content" || !normalizeLockToBlock(attrs.lockToBlock)) && attrs.clipEnd != null ? { "data-squisq-video-clip-end": String(attrs.clipEnd) } : {};
|
|
27725
|
+
}
|
|
27695
27726
|
},
|
|
27696
27727
|
// The HTML5 `controls` attribute is boolean-presence; parse its
|
|
27697
27728
|
// existence (even with an empty string value) as `true`, otherwise
|
|
@@ -27748,6 +27779,21 @@ var TiptapAudio = Node4.create({
|
|
|
27748
27779
|
default: true,
|
|
27749
27780
|
parseHTML: (el) => el.hasAttribute("controls"),
|
|
27750
27781
|
renderHTML: (attrs) => attrs.controls ? { controls: "" } : {}
|
|
27782
|
+
},
|
|
27783
|
+
startAt: {
|
|
27784
|
+
default: null,
|
|
27785
|
+
parseHTML: (el) => el.getAttribute("data-squisq-audio-start-at"),
|
|
27786
|
+
renderHTML: (attrs) => attrs.startAt != null ? { "data-squisq-audio-start-at": String(attrs.startAt) } : {}
|
|
27787
|
+
},
|
|
27788
|
+
clipStart: {
|
|
27789
|
+
default: null,
|
|
27790
|
+
parseHTML: (el) => el.getAttribute("data-squisq-audio-clip-start"),
|
|
27791
|
+
renderHTML: (attrs) => attrs.clipStart != null ? { "data-squisq-audio-clip-start": String(attrs.clipStart) } : {}
|
|
27792
|
+
},
|
|
27793
|
+
clipEnd: {
|
|
27794
|
+
default: null,
|
|
27795
|
+
parseHTML: (el) => el.getAttribute("data-squisq-audio-clip-end"),
|
|
27796
|
+
renderHTML: (attrs) => attrs.clipEnd != null ? { "data-squisq-audio-clip-end": String(attrs.clipEnd) } : {}
|
|
27751
27797
|
}
|
|
27752
27798
|
};
|
|
27753
27799
|
},
|
|
@@ -28145,8 +28191,8 @@ var LinkWithTitle = Link2.extend({
|
|
|
28145
28191
|
}
|
|
28146
28192
|
});
|
|
28147
28193
|
var EMPTY_PROMPTS = [
|
|
28148
|
-
"Start typing your content, or drop
|
|
28149
|
-
"Write anything -- paste markdown, drag in
|
|
28194
|
+
"Start typing your content, or drop files on top of me...",
|
|
28195
|
+
"Write anything -- paste markdown, drag in media, or just start typing...",
|
|
28150
28196
|
"Type away. Markdown syntax works too...",
|
|
28151
28197
|
"Chapter 1 begins here...",
|
|
28152
28198
|
"Once upon a time...",
|
|
@@ -28378,8 +28424,8 @@ function WysiwygEditor({
|
|
|
28378
28424
|
},
|
|
28379
28425
|
// When image files are dropped onto the editor, upload them via the
|
|
28380
28426
|
// MediaProvider and insert <img> nodes referencing the relative paths.
|
|
28381
|
-
//
|
|
28382
|
-
//
|
|
28427
|
+
// Drags from the MediaBin can also insert existing image, video, audio,
|
|
28428
|
+
// or generic-file references without uploading them again.
|
|
28383
28429
|
// Falls through to default handling for non-image drops or when no
|
|
28384
28430
|
// MediaProvider is available.
|
|
28385
28431
|
handleDrop: (view, event, _slice, moved) => {
|
|
@@ -28389,11 +28435,10 @@ function WysiwygEditor({
|
|
|
28389
28435
|
const squisqRaw = dt.getData(SQUISQ_MEDIA_MIME);
|
|
28390
28436
|
if (squisqRaw) {
|
|
28391
28437
|
const payload = parseSquisqMediaPayload(squisqRaw);
|
|
28392
|
-
if (payload
|
|
28438
|
+
if (payload) {
|
|
28393
28439
|
event.preventDefault();
|
|
28394
28440
|
moveSelectionToDropPoint(view, event);
|
|
28395
|
-
|
|
28396
|
-
return true;
|
|
28441
|
+
return insertExistingMediaReference(view, payload);
|
|
28397
28442
|
}
|
|
28398
28443
|
}
|
|
28399
28444
|
const imageFiles = filesFromDataTransfer(dt);
|
|
@@ -28720,6 +28765,30 @@ function insertImageNode(view, src, alt) {
|
|
|
28720
28765
|
const tr = view.state.tr.replaceSelectionWith(node2);
|
|
28721
28766
|
view.dispatch(tr);
|
|
28722
28767
|
}
|
|
28768
|
+
function insertExistingMediaReference(view, payload) {
|
|
28769
|
+
const kind = squisqMediaKind(payload.mimeType);
|
|
28770
|
+
if (kind === "image") {
|
|
28771
|
+
insertImageNode(view, payload.name, payload.alt);
|
|
28772
|
+
return true;
|
|
28773
|
+
}
|
|
28774
|
+
const { state } = view;
|
|
28775
|
+
if (kind === "video" || kind === "audio") {
|
|
28776
|
+
const nodeType = state.schema.nodes[kind];
|
|
28777
|
+
if (!nodeType) return false;
|
|
28778
|
+
const attrs = kind === "video" ? { src: payload.name, controls: true, width: 480 } : { src: payload.name, controls: true };
|
|
28779
|
+
const node3 = nodeType.create(attrs);
|
|
28780
|
+
const { $from } = state.selection;
|
|
28781
|
+
const pos = $from.depth >= 1 ? $from.after(1) : state.doc.content.size;
|
|
28782
|
+
view.dispatch(state.tr.insert(pos, node3).scrollIntoView());
|
|
28783
|
+
return true;
|
|
28784
|
+
}
|
|
28785
|
+
const linkType = state.schema.marks.link;
|
|
28786
|
+
if (!linkType) return false;
|
|
28787
|
+
const label = payload.alt || payload.name;
|
|
28788
|
+
const node2 = state.schema.text(label, [linkType.create({ href: payload.name })]);
|
|
28789
|
+
view.dispatch(state.tr.replaceSelectionWith(node2).scrollIntoView());
|
|
28790
|
+
return true;
|
|
28791
|
+
}
|
|
28723
28792
|
function moveSelectionToDropPoint(view, event) {
|
|
28724
28793
|
const coords = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
|
28725
28794
|
if (!coords) return;
|
|
@@ -30659,53 +30728,56 @@ function setHtmlAttribute(openingTag, name, value) {
|
|
|
30659
30728
|
if (attr.test(openingTag)) return openingTag.replace(attr, ` ${name}="${value}"`);
|
|
30660
30729
|
return openingTag.replace(/\s*\/?>(?=$)/, (end) => ` ${name}="${value}"${end.trimStart()}`);
|
|
30661
30730
|
}
|
|
30662
|
-
function
|
|
30663
|
-
|
|
30664
|
-
|
|
30731
|
+
function setHtmlMediaClipInLine(original, patch) {
|
|
30732
|
+
const tagMatch = /^\s*<(video|audio)\b/i.exec(original);
|
|
30733
|
+
if (!tagMatch) return null;
|
|
30734
|
+
const tag = tagMatch[1].toLowerCase();
|
|
30735
|
+
const opening = new RegExp(`<${tag}\\b[^>]*>`, "i").exec(original);
|
|
30665
30736
|
if (!opening) return null;
|
|
30666
30737
|
let next = opening[0];
|
|
30738
|
+
const timingPrefix = `data-squisq-${tag}`;
|
|
30667
30739
|
if (patch.startAt !== void 0) {
|
|
30668
30740
|
next = setHtmlAttribute(
|
|
30669
30741
|
next,
|
|
30670
|
-
|
|
30742
|
+
`${timingPrefix}-start-at`,
|
|
30671
30743
|
patch.startAt == null ? null : formatSeconds(patch.startAt)
|
|
30672
30744
|
);
|
|
30673
30745
|
}
|
|
30674
30746
|
if (patch.clipStart !== void 0) {
|
|
30675
30747
|
next = setHtmlAttribute(
|
|
30676
30748
|
next,
|
|
30677
|
-
|
|
30749
|
+
`${timingPrefix}-clip-start`,
|
|
30678
30750
|
patch.clipStart == null ? null : formatSeconds(patch.clipStart)
|
|
30679
30751
|
);
|
|
30680
30752
|
}
|
|
30681
30753
|
if (patch.clipEnd !== void 0) {
|
|
30682
30754
|
next = setHtmlAttribute(
|
|
30683
30755
|
next,
|
|
30684
|
-
|
|
30756
|
+
`${timingPrefix}-clip-end`,
|
|
30685
30757
|
patch.clipEnd == null ? null : formatSeconds(patch.clipEnd)
|
|
30686
30758
|
);
|
|
30687
30759
|
}
|
|
30688
|
-
if (patch.placement !== void 0) {
|
|
30760
|
+
if (tag === "video" && patch.placement !== void 0) {
|
|
30689
30761
|
next = setHtmlAttribute(
|
|
30690
30762
|
next,
|
|
30691
30763
|
"data-squisq-video-placement",
|
|
30692
30764
|
patch.placement == null || patch.placement === "content" ? null : patch.placement
|
|
30693
30765
|
);
|
|
30694
30766
|
}
|
|
30695
|
-
if (patch.lockToBlock !== void 0) {
|
|
30767
|
+
if (tag === "video" && patch.lockToBlock !== void 0) {
|
|
30696
30768
|
next = setHtmlAttribute(
|
|
30697
30769
|
next,
|
|
30698
30770
|
"data-squisq-video-lock-to-block",
|
|
30699
30771
|
patch.lockToBlock == null || patch.lockToBlock ? null : "false"
|
|
30700
30772
|
);
|
|
30701
30773
|
}
|
|
30702
|
-
if (patch.pipSize !== void 0) {
|
|
30774
|
+
if (tag === "video" && patch.pipSize !== void 0) {
|
|
30703
30775
|
next = setHtmlAttribute(next, "data-squisq-video-pip-size", patch.pipSize);
|
|
30704
30776
|
}
|
|
30705
|
-
if (patch.pipShape !== void 0) {
|
|
30777
|
+
if (tag === "video" && patch.pipShape !== void 0) {
|
|
30706
30778
|
next = setHtmlAttribute(next, "data-squisq-video-pip-shape", patch.pipShape);
|
|
30707
30779
|
}
|
|
30708
|
-
if (patch.pipPosition !== void 0) {
|
|
30780
|
+
if (tag === "video" && patch.pipPosition !== void 0) {
|
|
30709
30781
|
next = setHtmlAttribute(next, "data-squisq-video-pip-position", patch.pipPosition);
|
|
30710
30782
|
}
|
|
30711
30783
|
return `${original.slice(0, opening.index)}${next}${original.slice(opening.index + opening[0].length)}`;
|
|
@@ -30715,9 +30787,9 @@ function setMediaClipInSource(source, line, patch) {
|
|
|
30715
30787
|
const idx = line - 1;
|
|
30716
30788
|
if (idx < 0 || idx >= lines2.length) return null;
|
|
30717
30789
|
const original = lines2[idx];
|
|
30718
|
-
const
|
|
30719
|
-
if (
|
|
30720
|
-
lines2[idx] =
|
|
30790
|
+
const htmlMedia = setHtmlMediaClipInLine(original, patch);
|
|
30791
|
+
if (htmlMedia != null) {
|
|
30792
|
+
lines2[idx] = htmlMedia;
|
|
30721
30793
|
return lines2.join("\n");
|
|
30722
30794
|
}
|
|
30723
30795
|
const m = matchTrailingTemplateAnnotation3(original);
|
|
@@ -30797,8 +30869,27 @@ function placeClipInBlock(source, fromLine, targetHeadingLine, spec, startAt) {
|
|
|
30797
30869
|
if (fromIdx < 0 || fromIdx >= lines2.length) return null;
|
|
30798
30870
|
const targetIdx0 = targetHeadingLine - 1;
|
|
30799
30871
|
if (targetIdx0 < 0 || targetIdx0 >= lines2.length) return null;
|
|
30800
|
-
const
|
|
30801
|
-
|
|
30872
|
+
const normalizedStartAt = Math.max(0, startAt);
|
|
30873
|
+
const sameBlock = headingIndexAbove(lines2, fromIdx) === targetIdx0;
|
|
30874
|
+
if (/^\s*<(?:video|audio)\b/i.test(lines2[fromIdx])) {
|
|
30875
|
+
const html = setHtmlMediaClipInLine(lines2[fromIdx], {
|
|
30876
|
+
startAt: normalizedStartAt > 0 ? normalizedStartAt : null,
|
|
30877
|
+
...spec.clipStart !== void 0 ? { clipStart: spec.clipStart } : {},
|
|
30878
|
+
...spec.clipEnd !== void 0 ? { clipEnd: spec.clipEnd } : {}
|
|
30879
|
+
});
|
|
30880
|
+
if (html == null) return null;
|
|
30881
|
+
if (sameBlock) {
|
|
30882
|
+
lines2[fromIdx] = html;
|
|
30883
|
+
return lines2.join("\n");
|
|
30884
|
+
}
|
|
30885
|
+
lines2.splice(fromIdx, 1);
|
|
30886
|
+
let targetIdx2 = targetIdx0;
|
|
30887
|
+
if (fromIdx < targetIdx2) targetIdx2 -= 1;
|
|
30888
|
+
lines2.splice(targetIdx2 + 1, 0, "", html);
|
|
30889
|
+
return lines2.join("\n");
|
|
30890
|
+
}
|
|
30891
|
+
const annotation = buildClipAnnotation(spec, normalizedStartAt);
|
|
30892
|
+
if (sameBlock) {
|
|
30802
30893
|
lines2[fromIdx] = annotation;
|
|
30803
30894
|
return lines2.join("\n");
|
|
30804
30895
|
}
|
|
@@ -30820,6 +30911,7 @@ import { flattenBlocks as flattenBlocks6, DEFAULT_THEME as DEFAULT_THEME6, getPi
|
|
|
30820
30911
|
import { MediaClipLayer, MediaContext as MediaContext5 } from "@bendyline/squisq-react";
|
|
30821
30912
|
|
|
30822
30913
|
// src/embeddedMedia.ts
|
|
30914
|
+
import { parseTimeSeconds as parseTimeSeconds2 } from "@bendyline/squisq/markdown";
|
|
30823
30915
|
var VIDEO_EXT = /* @__PURE__ */ new Set(["webm", "mp4", "mov", "m4v", "ogv"]);
|
|
30824
30916
|
var AUDIO_EXT = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "oga", "m4a", "aac", "flac", "opus"]);
|
|
30825
30917
|
function mediaKindFromUrl(url) {
|
|
@@ -30829,6 +30921,11 @@ function mediaKindFromUrl(url) {
|
|
|
30829
30921
|
if (AUDIO_EXT.has(ext)) return "audio";
|
|
30830
30922
|
return null;
|
|
30831
30923
|
}
|
|
30924
|
+
function mediaTimeAttribute(attributes, kind, name) {
|
|
30925
|
+
const raw = attributes?.[`data-squisq-${kind}-${name}`];
|
|
30926
|
+
if (raw == null) return void 0;
|
|
30927
|
+
return parseTimeSeconds2(raw) ?? void 0;
|
|
30928
|
+
}
|
|
30832
30929
|
function collectEmbeddedMedia(block) {
|
|
30833
30930
|
const out = [];
|
|
30834
30931
|
const visit = (node2, line) => {
|
|
@@ -30851,7 +30948,19 @@ function collectEmbeddedMedia(block) {
|
|
|
30851
30948
|
}
|
|
30852
30949
|
}
|
|
30853
30950
|
}
|
|
30854
|
-
if (src)
|
|
30951
|
+
if (src) {
|
|
30952
|
+
const startAt = mediaTimeAttribute(n.attributes, kind, "start-at");
|
|
30953
|
+
const clipStart = mediaTimeAttribute(n.attributes, kind, "clip-start");
|
|
30954
|
+
const clipEnd = mediaTimeAttribute(n.attributes, kind, "clip-end");
|
|
30955
|
+
out.push({
|
|
30956
|
+
src,
|
|
30957
|
+
kind,
|
|
30958
|
+
sourceLine: here,
|
|
30959
|
+
...startAt != null ? { startAt } : {},
|
|
30960
|
+
...clipStart != null ? { clipStart } : {},
|
|
30961
|
+
...clipEnd != null ? { clipEnd } : {}
|
|
30962
|
+
});
|
|
30963
|
+
}
|
|
30855
30964
|
}
|
|
30856
30965
|
if (Array.isArray(n.children)) n.children.forEach((c) => visit(c, here));
|
|
30857
30966
|
if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach((c) => visit(c, here));
|
|
@@ -30859,18 +30968,30 @@ function collectEmbeddedMedia(block) {
|
|
|
30859
30968
|
(block.contents ?? []).forEach((node2) => visit(node2, void 0));
|
|
30860
30969
|
return out;
|
|
30861
30970
|
}
|
|
30971
|
+
function resolveEmbeddedMediaTiming(block, media) {
|
|
30972
|
+
const startAt = Math.max(0, media.startAt ?? 0);
|
|
30973
|
+
const sourceIn = Math.max(0, media.clipStart ?? 0);
|
|
30974
|
+
const absoluteStart = block.startTime + startAt;
|
|
30975
|
+
const blockEnd = block.startTime + block.duration;
|
|
30976
|
+
const authoredLength = media.clipEnd == null ? null : Math.max(0, media.clipEnd - sourceIn);
|
|
30977
|
+
const absoluteEnd = authoredLength == null ? blockEnd : Math.min(blockEnd, absoluteStart + authoredLength);
|
|
30978
|
+
return {
|
|
30979
|
+
absoluteStart,
|
|
30980
|
+
absoluteEnd: Math.max(absoluteStart, absoluteEnd),
|
|
30981
|
+
sourceIn
|
|
30982
|
+
};
|
|
30983
|
+
}
|
|
30862
30984
|
function collectEmbeddedMediaSchedule(doc) {
|
|
30863
30985
|
const schedule = [];
|
|
30864
30986
|
const visit = (blocks) => {
|
|
30865
30987
|
for (const block of blocks) {
|
|
30866
30988
|
collectEmbeddedMedia(block).forEach((media, index2) => {
|
|
30989
|
+
const timing = resolveEmbeddedMediaTiming(block, media);
|
|
30867
30990
|
schedule.push({
|
|
30868
30991
|
id: `embedded:${block.id}:${index2}`,
|
|
30869
30992
|
kind: media.kind,
|
|
30870
30993
|
src: media.src,
|
|
30871
|
-
|
|
30872
|
-
absoluteEnd: block.startTime + block.duration,
|
|
30873
|
-
sourceIn: 0,
|
|
30994
|
+
...timing,
|
|
30874
30995
|
anchor: "block",
|
|
30875
30996
|
blockId: block.id,
|
|
30876
30997
|
...media.sourceLine != null ? { sourceLine: media.sourceLine } : {}
|
|
@@ -31990,16 +32111,29 @@ function TimelineTrack({
|
|
|
31990
32111
|
blocks.flatMap(
|
|
31991
32112
|
(b, i) => collectEmbeddedMedia(b).map((m, j) => {
|
|
31992
32113
|
const id = `embed:${b.id}:${j}`;
|
|
31993
|
-
const
|
|
31994
|
-
const
|
|
32114
|
+
const timing = resolveEmbeddedMediaTiming(b, m);
|
|
32115
|
+
const absStart = timing.absoluteStart;
|
|
32116
|
+
const length = timing.absoluteEnd - timing.absoluteStart;
|
|
32117
|
+
const sourceIn = timing.sourceIn ?? 0;
|
|
32118
|
+
const startAt = Math.max(0, m.startAt ?? 0);
|
|
32119
|
+
const authoredLength = m.clipEnd == null ? null : Math.max(0, m.clipEnd - sourceIn);
|
|
32120
|
+
const previewBlockRemainder = Math.max(0, previewWidth(i) / pxPerSecond - startAt);
|
|
32121
|
+
const previewLength = authoredLength == null ? previewBlockRemainder : Math.min(authoredLength, previewBlockRemainder);
|
|
31995
32122
|
let left = previewLeft(i);
|
|
31996
32123
|
let clipWidth = previewWidth(i);
|
|
32124
|
+
left += startAt * pxPerSecond;
|
|
32125
|
+
clipWidth = Math.max(previewLength * pxPerSecond, 4);
|
|
31997
32126
|
if (drag?.targetId === id && drag.kind === "embed-move") {
|
|
31998
32127
|
left = drag.preview * pxPerSecond;
|
|
31999
32128
|
} else if (drag?.targetId === id && drag.kind === "embed-right") {
|
|
32000
32129
|
clipWidth = Math.max(drag.preview * pxPerSecond, 4);
|
|
32001
32130
|
}
|
|
32002
|
-
const spec = {
|
|
32131
|
+
const spec = {
|
|
32132
|
+
kind: m.kind,
|
|
32133
|
+
src: m.src,
|
|
32134
|
+
...m.clipStart != null ? { clipStart: m.clipStart } : {},
|
|
32135
|
+
...m.clipEnd != null ? { clipEnd: m.clipEnd } : {}
|
|
32136
|
+
};
|
|
32003
32137
|
return /* @__PURE__ */ jsx64(
|
|
32004
32138
|
"div",
|
|
32005
32139
|
{
|
|
@@ -32020,7 +32154,7 @@ function TimelineTrack({
|
|
|
32020
32154
|
(newAbsStart) => {
|
|
32021
32155
|
placeEmbeddedClip(
|
|
32022
32156
|
m.sourceLine,
|
|
32023
|
-
{ ...spec, clipEnd: length },
|
|
32157
|
+
{ ...spec, clipEnd: sourceIn + length },
|
|
32024
32158
|
newAbsStart
|
|
32025
32159
|
);
|
|
32026
32160
|
},
|
|
@@ -32031,7 +32165,7 @@ function TimelineTrack({
|
|
|
32031
32165
|
TimelineVideoFilmstrip,
|
|
32032
32166
|
{
|
|
32033
32167
|
src: m.src,
|
|
32034
|
-
sourceStart:
|
|
32168
|
+
sourceStart: sourceIn,
|
|
32035
32169
|
sourceLength: length,
|
|
32036
32170
|
width: clipWidth
|
|
32037
32171
|
}
|
|
@@ -32067,7 +32201,11 @@ function TimelineTrack({
|
|
|
32067
32201
|
{
|
|
32068
32202
|
className: "squisq-timeline-edge squisq-timeline-edge--right",
|
|
32069
32203
|
onPointerDown: (e2) => beginDrag(e2, "embed-right", id, length, (len) => {
|
|
32070
|
-
placeEmbeddedClip(
|
|
32204
|
+
placeEmbeddedClip(
|
|
32205
|
+
m.sourceLine,
|
|
32206
|
+
{ ...spec, clipEnd: sourceIn + len },
|
|
32207
|
+
absStart
|
|
32208
|
+
);
|
|
32071
32209
|
})
|
|
32072
32210
|
}
|
|
32073
32211
|
)
|
|
@@ -34406,18 +34544,16 @@ function MediaBin({
|
|
|
34406
34544
|
entries.map((entry) => {
|
|
34407
34545
|
const thumb = thumbUrls[entry.name];
|
|
34408
34546
|
const basename = basenameForPath(entry.name);
|
|
34409
|
-
const isImage = isImageMime(entry.mimeType);
|
|
34410
34547
|
const altText = basename.replace(/\.[^.]+$/, "").replace(/[-_]/g, " ");
|
|
34411
34548
|
const isUnused = !!usedMediaPaths && !usedMediaPaths.has(entry.name);
|
|
34412
34549
|
const handleDragStart = (e2) => {
|
|
34413
|
-
|
|
34414
|
-
const payload = JSON.stringify({
|
|
34550
|
+
const payload = {
|
|
34415
34551
|
name: entry.name,
|
|
34416
34552
|
mimeType: entry.mimeType,
|
|
34417
34553
|
alt: altText
|
|
34418
|
-
}
|
|
34419
|
-
e2.dataTransfer.setData(SQUISQ_MEDIA_MIME, payload);
|
|
34420
|
-
e2.dataTransfer.setData("text/plain",
|
|
34554
|
+
};
|
|
34555
|
+
e2.dataTransfer.setData(SQUISQ_MEDIA_MIME, JSON.stringify(payload));
|
|
34556
|
+
e2.dataTransfer.setData("text/plain", buildSquisqMediaReference(payload));
|
|
34421
34557
|
e2.dataTransfer.effectAllowed = "copy";
|
|
34422
34558
|
};
|
|
34423
34559
|
return /* @__PURE__ */ jsxs58(
|
|
@@ -34427,7 +34563,7 @@ function MediaBin({
|
|
|
34427
34563
|
title: `${entry.name}
|
|
34428
34564
|
${entry.mimeType}
|
|
34429
34565
|
${formatSize(entry.size)}`,
|
|
34430
|
-
draggable:
|
|
34566
|
+
draggable: true,
|
|
34431
34567
|
tabIndex: 0,
|
|
34432
34568
|
onContextMenu: (e2) => {
|
|
34433
34569
|
e2.preventDefault();
|
|
@@ -728,9 +728,10 @@ function serializeMediaTag(tag, attrs) {
|
|
|
728
728
|
const pipSize = tag === "video" ? /\bdata-squisq-video-pip-size="([^"]*)"/i.exec(attrs)?.[1] : void 0;
|
|
729
729
|
const pipShape = tag === "video" ? /\bdata-squisq-video-pip-shape="([^"]*)"/i.exec(attrs)?.[1] : void 0;
|
|
730
730
|
const pipPosition = tag === "video" ? /\bdata-squisq-video-pip-position="([^"]*)"/i.exec(attrs)?.[1] : void 0;
|
|
731
|
-
const
|
|
732
|
-
const
|
|
733
|
-
const
|
|
731
|
+
const timingPrefix = `data-squisq-${tag}`;
|
|
732
|
+
const startAt = new RegExp(`\\b${timingPrefix}-start-at="([^"]*)"`, "i").exec(attrs)?.[1];
|
|
733
|
+
const clipStart = new RegExp(`\\b${timingPrefix}-clip-start="([^"]*)"`, "i").exec(attrs)?.[1];
|
|
734
|
+
const clipEnd = new RegExp(`\\b${timingPrefix}-clip-end="([^"]*)"`, "i").exec(attrs)?.[1];
|
|
734
735
|
const parts = [`<${tag} src="${src}"`];
|
|
735
736
|
if (controls) parts.push(" controls");
|
|
736
737
|
if (width) parts.push(` width="${width}"`);
|
|
@@ -749,9 +750,9 @@ function serializeMediaTag(tag, attrs) {
|
|
|
749
750
|
if (pipPosition === "top-left" || pipPosition === "top-right" || pipPosition === "bottom-left" || pipPosition === "bottom-right") {
|
|
750
751
|
parts.push(` data-squisq-video-pip-position="${pipPosition}"`);
|
|
751
752
|
}
|
|
752
|
-
if (startAt != null) parts.push(`
|
|
753
|
-
if (clipStart != null) parts.push(`
|
|
754
|
-
if (clipEnd != null) parts.push(`
|
|
753
|
+
if (startAt != null) parts.push(` ${timingPrefix}-start-at="${startAt}"`);
|
|
754
|
+
if (clipStart != null) parts.push(` ${timingPrefix}-clip-start="${clipStart}"`);
|
|
755
|
+
if (clipEnd != null) parts.push(` ${timingPrefix}-clip-end="${clipEnd}"`);
|
|
755
756
|
parts.push(`></${tag}>`);
|
|
756
757
|
return parts.join("");
|
|
757
758
|
}
|
|
@@ -175,13 +175,18 @@ async function registerPcmWorklet(ctx) {
|
|
|
175
175
|
|
|
176
176
|
// src/teleprompter/useMicAnalysis.ts
|
|
177
177
|
import { useCallback, useEffect as useEffect2, useRef, useState } from "react";
|
|
178
|
-
function useMicAnalysis() {
|
|
178
|
+
function useMicAnalysis(constraints) {
|
|
179
179
|
const [status, setStatus] = useState("idle");
|
|
180
180
|
const [error, setError] = useState(null);
|
|
181
181
|
const [devices, setDevices] = useState([]);
|
|
182
182
|
const [stream, setStream] = useState(null);
|
|
183
183
|
const [sampleRate, setSampleRate] = useState(null);
|
|
184
184
|
const graphRef = useRef(null);
|
|
185
|
+
const constraintsRef = useRef(constraints);
|
|
186
|
+
constraintsRef.current = constraints;
|
|
187
|
+
const constraintKey = JSON.stringify(constraints ?? {});
|
|
188
|
+
const previousConstraintKeyRef = useRef(constraintKey);
|
|
189
|
+
const currentDeviceIdRef = useRef(null);
|
|
185
190
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
186
191
|
const generationRef = useRef(0);
|
|
187
192
|
const refreshDevices = useCallback(async () => {
|
|
@@ -226,13 +231,16 @@ function useMicAnalysis() {
|
|
|
226
231
|
const start = useCallback(
|
|
227
232
|
async (deviceId) => {
|
|
228
233
|
const generation = ++generationRef.current;
|
|
234
|
+
currentDeviceIdRef.current = deviceId;
|
|
229
235
|
teardown();
|
|
230
236
|
setStatus("starting");
|
|
231
237
|
setError(null);
|
|
232
238
|
try {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
239
|
+
const baseConstraints = constraintsRef.current;
|
|
240
|
+
const micStream = await requestMicStream({
|
|
241
|
+
...baseConstraints ?? {},
|
|
242
|
+
...deviceId ? { deviceId: { exact: deviceId } } : {}
|
|
243
|
+
});
|
|
236
244
|
if (generation !== generationRef.current) {
|
|
237
245
|
for (const track of micStream.getTracks()) track.stop();
|
|
238
246
|
return null;
|
|
@@ -311,6 +319,11 @@ function useMicAnalysis() {
|
|
|
311
319
|
listenersRef.current.delete(listener);
|
|
312
320
|
};
|
|
313
321
|
}, []);
|
|
322
|
+
useEffect2(() => {
|
|
323
|
+
if (previousConstraintKeyRef.current === constraintKey) return;
|
|
324
|
+
previousConstraintKeyRef.current = constraintKey;
|
|
325
|
+
if (graphRef.current) void start(currentDeviceIdRef.current);
|
|
326
|
+
}, [constraintKey, start]);
|
|
314
327
|
useEffect2(() => {
|
|
315
328
|
return () => {
|
|
316
329
|
generationRef.current += 1;
|
|
@@ -449,7 +462,7 @@ function useTeleprompter(opts) {
|
|
|
449
462
|
const [transport, setTransport] = useState2("stopped");
|
|
450
463
|
const [countdownRemaining, setCountdownRemaining] = useState2(null);
|
|
451
464
|
const [view, setView] = useState2({ wordPos: 0, micLevel: 0, voiceActive: false });
|
|
452
|
-
const mic = useMicAnalysis();
|
|
465
|
+
const mic = useMicAnalysis(opts.micConstraints);
|
|
453
466
|
const scriptRef = useRef2(script);
|
|
454
467
|
const prefsRef = useRef2(prefs);
|
|
455
468
|
const transportRef = useRef2(transport);
|
|
@@ -1429,20 +1442,36 @@ function useNarrationRecorder(options) {
|
|
|
1429
1442
|
const micStream = opts.mic.status === "live" && opts.mic.stream ? opts.mic.stream : await opts.mic.start(opts.getMicDeviceId());
|
|
1430
1443
|
if (superseded()) throw new StartAborted();
|
|
1431
1444
|
if (!micStream) throw opts.mic.error ?? new Error("Microphone unavailable");
|
|
1432
|
-
const audioFormat = resolveFormat("audio");
|
|
1445
|
+
const audioFormat = resolveFormat("audio", opts.audioRecorderOptions?.mimeType);
|
|
1446
|
+
const audioRecorderOptions = {
|
|
1447
|
+
...opts.audioRecorderOptions
|
|
1448
|
+
};
|
|
1449
|
+
if (audioFormat.mimeType) audioRecorderOptions.mimeType = audioFormat.mimeType;
|
|
1450
|
+
else delete audioRecorderOptions.mimeType;
|
|
1433
1451
|
audioRecorder = new MediaRecorder(
|
|
1434
1452
|
micStream,
|
|
1435
|
-
|
|
1453
|
+
Object.keys(audioRecorderOptions).length > 0 ? audioRecorderOptions : void 0
|
|
1436
1454
|
);
|
|
1437
1455
|
let cameraMime = null;
|
|
1438
1456
|
let cameraExt = null;
|
|
1439
1457
|
if (withCamera) {
|
|
1440
|
-
camera = await requestCameraStream({
|
|
1458
|
+
camera = await requestCameraStream({
|
|
1459
|
+
video: opts.cameraConstraints ?? true,
|
|
1460
|
+
audio: false
|
|
1461
|
+
});
|
|
1441
1462
|
if (superseded()) throw new StartAborted();
|
|
1442
|
-
const videoFormat = resolveFormat("video");
|
|
1463
|
+
const videoFormat = resolveFormat("video", opts.cameraRecorderOptions?.mimeType);
|
|
1464
|
+
const cameraRecorderOptions = {
|
|
1465
|
+
...opts.cameraRecorderOptions
|
|
1466
|
+
};
|
|
1467
|
+
if (cameraRecorderOptions.videoKeyFrameIntervalDuration !== void 0 && cameraRecorderOptions.videoKeyFrameIntervalCount !== void 0) {
|
|
1468
|
+
delete cameraRecorderOptions.videoKeyFrameIntervalCount;
|
|
1469
|
+
}
|
|
1470
|
+
if (videoFormat.mimeType) cameraRecorderOptions.mimeType = videoFormat.mimeType;
|
|
1471
|
+
else delete cameraRecorderOptions.mimeType;
|
|
1443
1472
|
cameraRecorder = new MediaRecorder(
|
|
1444
1473
|
camera,
|
|
1445
|
-
|
|
1474
|
+
Object.keys(cameraRecorderOptions).length > 0 ? cameraRecorderOptions : void 0
|
|
1446
1475
|
);
|
|
1447
1476
|
cameraMime = videoFormat.mimeType;
|
|
1448
1477
|
cameraExt = videoFormat.extension;
|
|
@@ -1775,7 +1804,7 @@ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMem
|
|
|
1775
1804
|
import { wordIndexAtTime } from "@bendyline/squisq/narration";
|
|
1776
1805
|
function useNarrationStage(opts) {
|
|
1777
1806
|
const { doc, recording = null, getAudioBasename } = opts;
|
|
1778
|
-
const controller = useTeleprompter({ doc });
|
|
1807
|
+
const controller = useTeleprompter({ doc, micConstraints: opts.micConstraints });
|
|
1779
1808
|
const float = useFloatingWindow(TELEPROMPTER_CSS);
|
|
1780
1809
|
const controllerRef = useRef5(controller);
|
|
1781
1810
|
controllerRef.current = controller;
|
|
@@ -1785,6 +1814,9 @@ function useNarrationStage(opts) {
|
|
|
1785
1814
|
getScript: () => controllerRef.current.script,
|
|
1786
1815
|
getWordPos: () => controllerRef.current.wordPos,
|
|
1787
1816
|
getMicDeviceId: () => controllerRef.current.prefs.micDeviceId,
|
|
1817
|
+
cameraConstraints: opts.cameraConstraints,
|
|
1818
|
+
audioRecorderOptions: opts.audioRecorderOptions,
|
|
1819
|
+
cameraRecorderOptions: opts.cameraRecorderOptions,
|
|
1788
1820
|
onRecordingStart: () => controllerRef.current.play(),
|
|
1789
1821
|
onRecordingStop: () => controllerRef.current.pause()
|
|
1790
1822
|
});
|