dsh-vidi 0.4.2 → 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/lib/client.js +527 -144
- package/lib/index.mjs +74 -30
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -776,6 +776,194 @@ function langLabel(code, ui, fallback) {
|
|
|
776
776
|
return NAMES[code]?.[ui] ?? fallback ?? code;
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
// src/client/Pick.tsx
|
|
780
|
+
/** Theme tokens copied onto the list, so it looks like the place it opened from (light page, dark player menu). */
|
|
781
|
+
const TOKENS = [
|
|
782
|
+
"--bg",
|
|
783
|
+
"--l1",
|
|
784
|
+
"--l2",
|
|
785
|
+
"--ink",
|
|
786
|
+
"--muted",
|
|
787
|
+
"--faint",
|
|
788
|
+
"--line",
|
|
789
|
+
"--hover",
|
|
790
|
+
"--accent"
|
|
791
|
+
];
|
|
792
|
+
/**
|
|
793
|
+
* A drop-down in the page's own style, in place of the browser's <select>
|
|
794
|
+
* (which draws a system menu as wide as the screen allows). The list is sized
|
|
795
|
+
* to its options, scrolls when long, opens upward when there is no room
|
|
796
|
+
* below, and works with the keyboard (arrows, Enter, Esc, typing a letter).
|
|
797
|
+
*/
|
|
798
|
+
function Pick(props) {
|
|
799
|
+
const { value, options, onChange, placeholder, disabled, className, title, label } = props;
|
|
800
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
801
|
+
const [active, setActive] = (0, react.useState)(-1);
|
|
802
|
+
const [place, setPlace] = (0, react.useState)({});
|
|
803
|
+
const btn = (0, react.useRef)(null);
|
|
804
|
+
const list = (0, react.useRef)(null);
|
|
805
|
+
const chosen = options.find((o) => o.value === value);
|
|
806
|
+
const usable = options.map((o, i) => o.disabled ? -1 : i).filter((i) => i >= 0);
|
|
807
|
+
const close = (focus = true) => {
|
|
808
|
+
setOpen(false);
|
|
809
|
+
if (focus) btn.current?.focus();
|
|
810
|
+
};
|
|
811
|
+
const choose = (o) => {
|
|
812
|
+
if (o.disabled) return;
|
|
813
|
+
onChange(o.value);
|
|
814
|
+
close();
|
|
815
|
+
};
|
|
816
|
+
(0, react.useLayoutEffect)(() => {
|
|
817
|
+
if (!open || !btn.current) return;
|
|
818
|
+
const r = btn.current.getBoundingClientRect();
|
|
819
|
+
const cs = getComputedStyle(btn.current);
|
|
820
|
+
const vars = Object.fromEntries(TOKENS.map((k) => [k, cs.getPropertyValue(k).trim()]).filter(([, v]) => v));
|
|
821
|
+
const room = window.innerHeight - r.bottom - 8, above = r.top - 8;
|
|
822
|
+
const up = room < 220 && above > room;
|
|
823
|
+
const maxH = Math.max(120, Math.min(320, up ? above - 4 : room - 4));
|
|
824
|
+
const width = Math.min(Math.max(r.width, 180), window.innerWidth - 16);
|
|
825
|
+
const left = Math.min(Math.max(8, r.left), window.innerWidth - width - 8);
|
|
826
|
+
setPlace({
|
|
827
|
+
...vars,
|
|
828
|
+
left,
|
|
829
|
+
width,
|
|
830
|
+
maxHeight: maxH,
|
|
831
|
+
...up ? { bottom: window.innerHeight - r.top + 4 } : { top: r.bottom + 4 },
|
|
832
|
+
fontFamily: cs.fontFamily
|
|
833
|
+
});
|
|
834
|
+
setActive(Math.max(0, options.findIndex((o) => o.value === value)));
|
|
835
|
+
}, [open]);
|
|
836
|
+
(0, react.useEffect)(() => {
|
|
837
|
+
list.current?.querySelector(`[data-i="${active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
838
|
+
}, [active, open]);
|
|
839
|
+
(0, react.useEffect)(() => {
|
|
840
|
+
if (!open) return;
|
|
841
|
+
const away = (e) => {
|
|
842
|
+
if (!list.current?.contains(e.target) && !btn.current?.contains(e.target)) close(false);
|
|
843
|
+
};
|
|
844
|
+
const gone = (e) => {
|
|
845
|
+
if (!list.current?.contains(e.target)) close(false);
|
|
846
|
+
};
|
|
847
|
+
document.addEventListener("pointerdown", away, true);
|
|
848
|
+
window.addEventListener("resize", gone);
|
|
849
|
+
window.addEventListener("scroll", gone, true);
|
|
850
|
+
return () => {
|
|
851
|
+
document.removeEventListener("pointerdown", away, true);
|
|
852
|
+
window.removeEventListener("resize", gone);
|
|
853
|
+
window.removeEventListener("scroll", gone, true);
|
|
854
|
+
};
|
|
855
|
+
}, [open]);
|
|
856
|
+
const onKey = (e) => {
|
|
857
|
+
if (disabled) return;
|
|
858
|
+
const step = (d) => {
|
|
859
|
+
const at = usable.indexOf(active);
|
|
860
|
+
setActive(usable[Math.min(usable.length - 1, Math.max(0, (at < 0 ? d > 0 ? -1 : usable.length : at) + d))] ?? -1);
|
|
861
|
+
};
|
|
862
|
+
if (!open) {
|
|
863
|
+
if ([
|
|
864
|
+
"ArrowDown",
|
|
865
|
+
"ArrowUp",
|
|
866
|
+
"Enter",
|
|
867
|
+
" "
|
|
868
|
+
].includes(e.key)) {
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
setOpen(true);
|
|
871
|
+
}
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (e.key === "Escape") {
|
|
875
|
+
e.preventDefault();
|
|
876
|
+
e.stopPropagation();
|
|
877
|
+
close();
|
|
878
|
+
} else if (e.key === "ArrowDown") {
|
|
879
|
+
e.preventDefault();
|
|
880
|
+
step(1);
|
|
881
|
+
} else if (e.key === "ArrowUp") {
|
|
882
|
+
e.preventDefault();
|
|
883
|
+
step(-1);
|
|
884
|
+
} else if (e.key === "Home") {
|
|
885
|
+
e.preventDefault();
|
|
886
|
+
setActive(usable[0] ?? -1);
|
|
887
|
+
} else if (e.key === "End") {
|
|
888
|
+
e.preventDefault();
|
|
889
|
+
setActive(usable.at(-1) ?? -1);
|
|
890
|
+
} else if (e.key === "Enter" || e.key === " ") {
|
|
891
|
+
e.preventDefault();
|
|
892
|
+
const o = options[active];
|
|
893
|
+
if (o) choose(o);
|
|
894
|
+
} else if (e.key === "Tab") close(false);
|
|
895
|
+
else if (e.key.length === 1) {
|
|
896
|
+
const k = e.key.toLowerCase();
|
|
897
|
+
const from = usable.indexOf(active);
|
|
898
|
+
const hit = [...usable.slice(from + 1), ...usable.slice(0, from + 1)].find((i) => options[i].label.toLowerCase().startsWith(k));
|
|
899
|
+
if (hit !== void 0) setActive(hit);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
let lastGroup;
|
|
903
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
904
|
+
ref: btn,
|
|
905
|
+
type: "button",
|
|
906
|
+
className: `dshl-select ${open ? "open" : ""} ${className ?? ""}`,
|
|
907
|
+
disabled,
|
|
908
|
+
title,
|
|
909
|
+
"aria-haspopup": "listbox",
|
|
910
|
+
"aria-expanded": open,
|
|
911
|
+
onClick: () => setOpen((v) => !v),
|
|
912
|
+
onKeyDown: onKey,
|
|
913
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
914
|
+
className: "v",
|
|
915
|
+
children: label ?? chosen?.label ?? placeholder ?? ""
|
|
916
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
917
|
+
className: "caret",
|
|
918
|
+
"aria-hidden": "true"
|
|
919
|
+
})]
|
|
920
|
+
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
921
|
+
ref: list,
|
|
922
|
+
className: "dshl-options",
|
|
923
|
+
role: "listbox",
|
|
924
|
+
style: place,
|
|
925
|
+
onKeyDown: onKey,
|
|
926
|
+
children: options.map((o, i) => {
|
|
927
|
+
const head = o.group && o.group !== lastGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
928
|
+
className: "grp",
|
|
929
|
+
children: o.group
|
|
930
|
+
}, `g:${o.group}`) : null;
|
|
931
|
+
lastGroup = o.group;
|
|
932
|
+
return [head, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
933
|
+
"data-i": i,
|
|
934
|
+
role: "option",
|
|
935
|
+
"aria-selected": o.value === value,
|
|
936
|
+
"aria-disabled": o.disabled || void 0,
|
|
937
|
+
className: `opt ${o.value === value ? "on" : ""} ${i === active ? "act" : ""} ${o.disabled ? "off" : ""}`,
|
|
938
|
+
onPointerEnter: () => !o.disabled && setActive(i),
|
|
939
|
+
onClick: () => choose(o),
|
|
940
|
+
children: [
|
|
941
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
942
|
+
className: "l",
|
|
943
|
+
children: o.label
|
|
944
|
+
}),
|
|
945
|
+
o.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
946
|
+
className: "h",
|
|
947
|
+
children: o.hint
|
|
948
|
+
}),
|
|
949
|
+
o.value === value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
950
|
+
className: "ok",
|
|
951
|
+
viewBox: "0 0 24 24",
|
|
952
|
+
width: "14",
|
|
953
|
+
height: "14",
|
|
954
|
+
fill: "none",
|
|
955
|
+
stroke: "currentColor",
|
|
956
|
+
strokeWidth: "2.4",
|
|
957
|
+
strokeLinecap: "round",
|
|
958
|
+
strokeLinejoin: "round",
|
|
959
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 12.5l4.5 4.5L19 7.5" })
|
|
960
|
+
})
|
|
961
|
+
]
|
|
962
|
+
}, o.value)];
|
|
963
|
+
})
|
|
964
|
+
}), document.fullscreenElement ?? document.body)] });
|
|
965
|
+
}
|
|
966
|
+
|
|
779
967
|
// src/client/LineChips.tsx
|
|
780
968
|
const MAX_TRANSLATIONS = 4;
|
|
781
969
|
/**
|
|
@@ -810,20 +998,18 @@ function LineChips(props) {
|
|
|
810
998
|
children: [i >= 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { children: i + 1 }), label(k)]
|
|
811
999
|
}, k);
|
|
812
1000
|
}),
|
|
813
|
-
translations.length < MAX_TRANSLATIONS && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1001
|
+
translations.length < MAX_TRANSLATIONS && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
814
1002
|
className: "chip add",
|
|
815
1003
|
value: "",
|
|
816
|
-
|
|
817
|
-
if (e.target.value) onChange([...lines, e.target.value]);
|
|
818
|
-
},
|
|
1004
|
+
label: t("sub.addLang"),
|
|
819
1005
|
title: t("in.addLang"),
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1006
|
+
onChange: (v) => {
|
|
1007
|
+
if (v) onChange([...lines, v]);
|
|
1008
|
+
},
|
|
1009
|
+
options: options.filter(([k]) => !lines.includes(k)).map(([k, v]) => ({
|
|
824
1010
|
value: k,
|
|
825
|
-
|
|
826
|
-
}
|
|
1011
|
+
label: v
|
|
1012
|
+
}))
|
|
827
1013
|
}),
|
|
828
1014
|
lines.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
829
1015
|
className: "chip ghost",
|
|
@@ -860,30 +1046,26 @@ function SubtitleMenu(props) {
|
|
|
860
1046
|
const second = lines[1] ?? "";
|
|
861
1047
|
const label = (k) => site.find((s) => s.key === k)?.label ?? name(k);
|
|
862
1048
|
const inUse = lines.filter((l) => l !== "src" && !l.startsWith("site:"));
|
|
863
|
-
const choices =
|
|
864
|
-
|
|
1049
|
+
const choices = [
|
|
1050
|
+
{
|
|
865
1051
|
value: "src",
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
inUse.map((k) =>
|
|
1052
|
+
label: label("src")
|
|
1053
|
+
},
|
|
1054
|
+
...inUse.map((k) => ({
|
|
869
1055
|
value: k,
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
site.
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
children: v
|
|
884
|
-
}, k))
|
|
885
|
-
})
|
|
886
|
-
] });
|
|
1056
|
+
label: label(k)
|
|
1057
|
+
})),
|
|
1058
|
+
...site.map((s) => ({
|
|
1059
|
+
value: s.key,
|
|
1060
|
+
label: s.label,
|
|
1061
|
+
group: t("sub.siteGroup")
|
|
1062
|
+
})),
|
|
1063
|
+
...options.filter(([k]) => !inUse.includes(k)).map(([k, v]) => ({
|
|
1064
|
+
value: k,
|
|
1065
|
+
label: v,
|
|
1066
|
+
group: t("sub.translateTo")
|
|
1067
|
+
}))
|
|
1068
|
+
];
|
|
887
1069
|
const setMain = (v) => onLines(v === second ? [v, main] : second ? [v, second] : [v]);
|
|
888
1070
|
const setSecond = (v) => onLines(!v ? [main] : v === main ? [main] : [main, v]);
|
|
889
1071
|
const first = style.lines.find((l) => l.show)?.size ?? SIZES.m[0];
|
|
@@ -903,21 +1085,21 @@ function SubtitleMenu(props) {
|
|
|
903
1085
|
children: [
|
|
904
1086
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
905
1087
|
className: "slot",
|
|
906
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sub.main") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1088
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sub.main") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
907
1089
|
value: main,
|
|
908
|
-
onChange:
|
|
909
|
-
|
|
1090
|
+
onChange: setMain,
|
|
1091
|
+
options: choices
|
|
910
1092
|
})]
|
|
911
1093
|
}),
|
|
912
1094
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
913
1095
|
className: "slot",
|
|
914
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sub.second") }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1096
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sub.second") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
915
1097
|
value: second,
|
|
916
|
-
onChange:
|
|
917
|
-
|
|
1098
|
+
onChange: setSecond,
|
|
1099
|
+
options: [{
|
|
918
1100
|
value: "",
|
|
919
|
-
|
|
920
|
-
}
|
|
1101
|
+
label: t("sub.none")
|
|
1102
|
+
}, ...choices]
|
|
921
1103
|
})]
|
|
922
1104
|
}),
|
|
923
1105
|
second && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -1016,13 +1198,13 @@ function LoginDialog(props) {
|
|
|
1016
1198
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("login.body") }),
|
|
1017
1199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1018
1200
|
className: "srow",
|
|
1019
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("login.from") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1201
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("login.from") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
1020
1202
|
value: browser,
|
|
1021
|
-
onChange:
|
|
1022
|
-
|
|
1203
|
+
onChange: onBrowser,
|
|
1204
|
+
options: browsers.map((b) => ({
|
|
1023
1205
|
value: b.id,
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1206
|
+
label: b.label
|
|
1207
|
+
}))
|
|
1026
1208
|
})]
|
|
1027
1209
|
}),
|
|
1028
1210
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -33366,8 +33548,7 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
33366
33548
|
backBufferLength: Infinity
|
|
33367
33549
|
} : {
|
|
33368
33550
|
liveSyncDuration: delay,
|
|
33369
|
-
|
|
33370
|
-
backBufferLength: 1800
|
|
33551
|
+
backBufferLength: Infinity
|
|
33371
33552
|
});
|
|
33372
33553
|
hls.current = h;
|
|
33373
33554
|
h.loadSource(src);
|
|
@@ -33445,7 +33626,7 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
33445
33626
|
(0, react.useEffect)(() => {
|
|
33446
33627
|
if (!menu) return;
|
|
33447
33628
|
const close = (e) => {
|
|
33448
|
-
if (!e.target.closest?.(".dshl-menu, .dshl-menubtn")) setMenu(null);
|
|
33629
|
+
if (!e.target.closest?.(".dshl-menu, .dshl-menubtn, .dshl-options")) setMenu(null);
|
|
33449
33630
|
};
|
|
33450
33631
|
window.addEventListener("mousedown", close);
|
|
33451
33632
|
return () => window.removeEventListener("mousedown", close);
|
|
@@ -33490,7 +33671,6 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
33490
33671
|
const h = hls.current, el = video.current;
|
|
33491
33672
|
if (h && el) {
|
|
33492
33673
|
h.config.liveSyncDuration = v;
|
|
33493
|
-
h.config.liveMaxLatencyDuration = v + 60;
|
|
33494
33674
|
const pos = h.liveSyncPosition;
|
|
33495
33675
|
if (pos != null) el.currentTime = pos;
|
|
33496
33676
|
}
|
|
@@ -34107,17 +34287,17 @@ function RestreamPanel(props) {
|
|
|
34107
34287
|
}),
|
|
34108
34288
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
34109
34289
|
className: "rs-field",
|
|
34110
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("rs.platform") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
34290
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("rs.platform") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
34111
34291
|
value: editing.platform,
|
|
34112
|
-
onChange: (
|
|
34292
|
+
onChange: (v) => setEditing({
|
|
34113
34293
|
...editing,
|
|
34114
|
-
platform:
|
|
34115
|
-
server: FIXED[
|
|
34294
|
+
platform: v,
|
|
34295
|
+
server: FIXED[v] ?? (FIXED[editing.platform] === editing.server ? "" : editing.server)
|
|
34116
34296
|
}),
|
|
34117
|
-
|
|
34297
|
+
options: PLATFORMS.map((p) => ({
|
|
34118
34298
|
value: p,
|
|
34119
|
-
|
|
34120
|
-
}
|
|
34299
|
+
label: pname(p)
|
|
34300
|
+
}))
|
|
34121
34301
|
})]
|
|
34122
34302
|
}),
|
|
34123
34303
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -34319,7 +34499,7 @@ function Settings(props) {
|
|
|
34319
34499
|
(0, react.useEffect)(() => {
|
|
34320
34500
|
if (!open) return;
|
|
34321
34501
|
const close = (e) => {
|
|
34322
|
-
if (!box.current?.contains(e.target)) onOpen(false);
|
|
34502
|
+
if (!box.current?.contains(e.target) && !e.target.closest?.(".dshl-options")) onOpen(false);
|
|
34323
34503
|
};
|
|
34324
34504
|
window.addEventListener("mousedown", close);
|
|
34325
34505
|
return () => window.removeEventListener("mousedown", close);
|
|
@@ -34394,14 +34574,15 @@ function Settings(props) {
|
|
|
34394
34574
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34395
34575
|
title: t("set.engine.tip"),
|
|
34396
34576
|
children: t("set.engine")
|
|
34397
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
34577
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
34398
34578
|
value: engine,
|
|
34399
34579
|
disabled,
|
|
34400
|
-
onChange:
|
|
34401
|
-
|
|
34580
|
+
onChange: onEngine,
|
|
34581
|
+
options: providers.map((p) => ({
|
|
34402
34582
|
value: p.id,
|
|
34403
|
-
|
|
34404
|
-
|
|
34583
|
+
label: name(p.id),
|
|
34584
|
+
hint: p.ready ? void 0 : t(p.online ? "set.engine.noKey" : "set.engine.notInstalled")
|
|
34585
|
+
}))
|
|
34405
34586
|
})]
|
|
34406
34587
|
}),
|
|
34407
34588
|
cur?.online && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -34475,19 +34656,18 @@ function Settings(props) {
|
|
|
34475
34656
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34476
34657
|
title: t("set.model.tip"),
|
|
34477
34658
|
children: t("set.model")
|
|
34478
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
34659
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
34479
34660
|
value: models?.chosen ? modelKey(models.chosen) : "",
|
|
34480
|
-
onChange:
|
|
34481
|
-
|
|
34661
|
+
onChange: chooseModel,
|
|
34662
|
+
options: [{
|
|
34482
34663
|
value: "",
|
|
34483
|
-
|
|
34484
|
-
}
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
|
|
34489
|
-
|
|
34490
|
-
}, pn))]
|
|
34664
|
+
label: models?.fallback ? t("set.model.default").replace("{m}", modelName(models.fallback)) : t("set.model.dsh")
|
|
34665
|
+
}, ...providersOf.flatMap((pn) => models.models.filter((m) => m.providerName === pn).map((m) => ({
|
|
34666
|
+
value: modelKey(m),
|
|
34667
|
+
label: m.name,
|
|
34668
|
+
hint: m.image ? t("set.model.image") : void 0,
|
|
34669
|
+
group: pn
|
|
34670
|
+
})))]
|
|
34491
34671
|
})]
|
|
34492
34672
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34493
34673
|
className: "hint",
|
|
@@ -34495,56 +34675,61 @@ function Settings(props) {
|
|
|
34495
34675
|
})]
|
|
34496
34676
|
}),
|
|
34497
34677
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34498
|
-
className: "srow",
|
|
34499
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
34500
|
-
className: "
|
|
34501
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
34502
|
-
className:
|
|
34503
|
-
|
|
34504
|
-
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
|
|
34508
|
-
|
|
34509
|
-
|
|
34510
|
-
|
|
34678
|
+
className: "srow col",
|
|
34679
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
34680
|
+
className: "pair",
|
|
34681
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("set.mode") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34682
|
+
className: "dshl-seg",
|
|
34683
|
+
children: [
|
|
34684
|
+
"site",
|
|
34685
|
+
"realtime",
|
|
34686
|
+
"polished"
|
|
34687
|
+
].map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34688
|
+
className: mode === m ? "on" : "",
|
|
34689
|
+
title: t(`mode.${m}.tip`),
|
|
34690
|
+
onClick: () => onMode(m),
|
|
34691
|
+
children: t(`mode.${m}`)
|
|
34692
|
+
}, m))
|
|
34511
34693
|
})]
|
|
34694
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34695
|
+
className: "hint",
|
|
34696
|
+
children: t(`mode.${mode}.tip`)
|
|
34512
34697
|
})]
|
|
34513
34698
|
}),
|
|
34514
34699
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34515
34700
|
className: "srow",
|
|
34516
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("set.spoken") }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
34701
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("set.spoken") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
34517
34702
|
value: spoken,
|
|
34518
34703
|
disabled,
|
|
34519
|
-
onChange:
|
|
34520
|
-
|
|
34704
|
+
onChange: onSpoken,
|
|
34705
|
+
options: [{
|
|
34521
34706
|
value: "auto",
|
|
34522
|
-
|
|
34523
|
-
}
|
|
34707
|
+
label: t("set.auto")
|
|
34708
|
+
}, ...langOptions.map(([k, v]) => ({
|
|
34524
34709
|
value: k,
|
|
34525
|
-
|
|
34526
|
-
}
|
|
34710
|
+
label: v
|
|
34711
|
+
}))]
|
|
34527
34712
|
})]
|
|
34528
34713
|
}),
|
|
34529
34714
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34530
34715
|
className: "srow",
|
|
34531
34716
|
title: t("set.login.tip"),
|
|
34532
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("set.login") }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
34717
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("set.login") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pick, {
|
|
34533
34718
|
value: login,
|
|
34534
|
-
onChange:
|
|
34535
|
-
|
|
34536
|
-
|
|
34719
|
+
onChange: onLogin,
|
|
34720
|
+
options: [
|
|
34721
|
+
{
|
|
34537
34722
|
value: "auto",
|
|
34538
|
-
|
|
34539
|
-
}
|
|
34540
|
-
browsers.map((b) =>
|
|
34723
|
+
label: t("set.auto")
|
|
34724
|
+
},
|
|
34725
|
+
...browsers.map((b) => ({
|
|
34541
34726
|
value: b.id,
|
|
34542
|
-
|
|
34543
|
-
}
|
|
34544
|
-
|
|
34727
|
+
label: b.label
|
|
34728
|
+
})),
|
|
34729
|
+
{
|
|
34545
34730
|
value: "none",
|
|
34546
|
-
|
|
34547
|
-
}
|
|
34731
|
+
label: t("set.login.none")
|
|
34732
|
+
}
|
|
34548
34733
|
]
|
|
34549
34734
|
})]
|
|
34550
34735
|
})
|
|
@@ -34649,7 +34834,7 @@ function LiveView(props) {
|
|
|
34649
34834
|
setLinesRaw(l);
|
|
34650
34835
|
store.set("lines", l);
|
|
34651
34836
|
};
|
|
34652
|
-
const [mode, setModeRaw] = (0, react.useState)(() => store.get("mode", "
|
|
34837
|
+
const [mode, setModeRaw] = (0, react.useState)(() => store.get("mode", "polished"));
|
|
34653
34838
|
const setMode = (m) => {
|
|
34654
34839
|
setModeRaw(m);
|
|
34655
34840
|
store.set("mode", m);
|
|
@@ -34702,6 +34887,13 @@ function LiveView(props) {
|
|
|
34702
34887
|
t,
|
|
34703
34888
|
ui
|
|
34704
34889
|
]);
|
|
34890
|
+
const siteBest = (0, react.useMemo)(() => {
|
|
34891
|
+
const list = job?.site ?? [];
|
|
34892
|
+
const base = (l) => l.replace(/-orig$/, "").split("-")[0];
|
|
34893
|
+
return (list.find((x) => !x.auto && base(x.lang) === ui) ?? list.find((x) => !x.auto) ?? list.find((x) => base(x.lang) === ui) ?? list[0])?.key;
|
|
34894
|
+
}, [job?.site, ui]);
|
|
34895
|
+
const siteOnly = mode === "site" && !!job;
|
|
34896
|
+
const screenLines = siteOnly && siteBest ? lines.some((l) => l.startsWith("site:")) ? lines.filter((l) => l.startsWith("site:")) : [siteBest] : lines;
|
|
34705
34897
|
const shownLines = lines;
|
|
34706
34898
|
/** The chosen recognition, or — when it can't run here (no key, not installed) — the first one that can. */
|
|
34707
34899
|
const usableEngine = () => {
|
|
@@ -34738,8 +34930,8 @@ function LiveView(props) {
|
|
|
34738
34930
|
}).catch(() => {});
|
|
34739
34931
|
}, [sessionId]);
|
|
34740
34932
|
(0, react.useEffect)(() => {
|
|
34741
|
-
setSubStyle((s) => reconcile(s,
|
|
34742
|
-
}, [
|
|
34933
|
+
setSubStyle((s) => reconcile(s, screenLines));
|
|
34934
|
+
}, [screenLines.join(",")]);
|
|
34743
34935
|
(0, react.useEffect)(() => {
|
|
34744
34936
|
saveStyle(subStyle);
|
|
34745
34937
|
}, [subStyle]);
|
|
@@ -34838,6 +35030,7 @@ function LiveView(props) {
|
|
|
34838
35030
|
url: target
|
|
34839
35031
|
} : classify(target);
|
|
34840
35032
|
if (!c) return;
|
|
35033
|
+
if (props.onNewVideo && job && job.source !== "push" && job.url !== c.url && c.source !== "push" && await props.onNewVideo(target)) return;
|
|
34841
35034
|
try {
|
|
34842
35035
|
const j = await post("start", {
|
|
34843
35036
|
...options(),
|
|
@@ -34862,7 +35055,39 @@ function LiveView(props) {
|
|
|
34862
35055
|
state: "off",
|
|
34863
35056
|
at: 0
|
|
34864
35057
|
};
|
|
34865
|
-
|
|
35058
|
+
(0, react.useEffect)(() => {
|
|
35059
|
+
if (!job?.title || !props.onTitle) return;
|
|
35060
|
+
const key = `dsh-live.named.${sessionId}`;
|
|
35061
|
+
try {
|
|
35062
|
+
if (localStorage.getItem(key) === job.id) return;
|
|
35063
|
+
localStorage.setItem(key, job.id);
|
|
35064
|
+
} catch {}
|
|
35065
|
+
props.onTitle(job.title);
|
|
35066
|
+
}, [job?.id, job?.title]);
|
|
35067
|
+
const rateLog = (0, react.useRef)([]);
|
|
35068
|
+
const [rate, setRate] = (0, react.useState)(null);
|
|
35069
|
+
(0, react.useEffect)(() => {
|
|
35070
|
+
if (!job || job.vod || job.status !== "running" || subs.state !== "running") {
|
|
35071
|
+
rateLog.current = [];
|
|
35072
|
+
setRate(null);
|
|
35073
|
+
return;
|
|
35074
|
+
}
|
|
35075
|
+
const log = rateLog.current;
|
|
35076
|
+
log.push({
|
|
35077
|
+
wall: Date.now(),
|
|
35078
|
+
at: subs.at
|
|
35079
|
+
});
|
|
35080
|
+
while (log.length > 2 && log[0].wall < Date.now() - 12e4) log.shift();
|
|
35081
|
+
const first = log[0], last = log.at(-1);
|
|
35082
|
+
setRate(last.wall - first.wall > 45e3 ? (last.at - first.at) / ((last.wall - first.wall) / 1e3) : null);
|
|
35083
|
+
}, [
|
|
35084
|
+
subs.at,
|
|
35085
|
+
subs.state,
|
|
35086
|
+
job?.status,
|
|
35087
|
+
job?.id
|
|
35088
|
+
]);
|
|
35089
|
+
const emptyLines = subs.state === "running" && cues.length === 0 && subs.at > 15 ? t("tr.listening").replace("{t}", CLOCK(subs.at)) : t("tr.empty");
|
|
35090
|
+
const startSubs = async (from, withMode) => {
|
|
34866
35091
|
if (!job) return;
|
|
34867
35092
|
setError("");
|
|
34868
35093
|
const p = providers.find((x) => x.id === usableEngine());
|
|
@@ -34874,6 +35099,7 @@ function LiveView(props) {
|
|
|
34874
35099
|
try {
|
|
34875
35100
|
setJob(await post("subs", {
|
|
34876
35101
|
...options(),
|
|
35102
|
+
...withMode ? { mode: withMode } : {},
|
|
34877
35103
|
jobId: job.id,
|
|
34878
35104
|
action: "start",
|
|
34879
35105
|
from
|
|
@@ -34888,6 +35114,17 @@ function LiveView(props) {
|
|
|
34888
35114
|
action: "pause"
|
|
34889
35115
|
}).catch(() => job));
|
|
34890
35116
|
};
|
|
35117
|
+
/** Switching modes while a video is open takes effect right away: no delay stops transcribing, the others (re)start it. */
|
|
35118
|
+
const changeMode = (m) => {
|
|
35119
|
+
if (m === mode) return;
|
|
35120
|
+
setMode(m);
|
|
35121
|
+
if (!job || job.status === "error") return;
|
|
35122
|
+
if (m === "site") {
|
|
35123
|
+
if (subs.state === "running") pauseSubs();
|
|
35124
|
+
return;
|
|
35125
|
+
}
|
|
35126
|
+
startSubs(subs.state === "off" ? now : void 0, m);
|
|
35127
|
+
};
|
|
34891
35128
|
const changeLines = async (next) => {
|
|
34892
35129
|
const before = translationsOf(lines).join(","), after = translationsOf(next).join(",");
|
|
34893
35130
|
setLines(next);
|
|
@@ -34901,6 +35138,16 @@ function LiveView(props) {
|
|
|
34901
35138
|
const stop = async () => {
|
|
34902
35139
|
if (job) await post("stop", { jobId: job.id }).catch(() => {});
|
|
34903
35140
|
};
|
|
35141
|
+
/** Leave this video: everything for it stops, the page is empty again (its files stay in the video folder). */
|
|
35142
|
+
const closeVideo = async () => {
|
|
35143
|
+
if (!job) return;
|
|
35144
|
+
await post("close", { jobId: job.id }).catch(() => {});
|
|
35145
|
+
setJob(null);
|
|
35146
|
+
setCues([]);
|
|
35147
|
+
setTerms([]);
|
|
35148
|
+
setInput("");
|
|
35149
|
+
setBench(null);
|
|
35150
|
+
};
|
|
34904
35151
|
const onFile = async (file) => {
|
|
34905
35152
|
setError("");
|
|
34906
35153
|
setUploading(0);
|
|
@@ -34932,7 +35179,7 @@ function LiveView(props) {
|
|
|
34932
35179
|
const translatable = langOptions.filter(([k]) => k !== spokenBase);
|
|
34933
35180
|
const nameWithSpoken = (k) => k === "src" && spokenBase ? `${t("sub.source")} · ${langName(spokenBase)}` : langName(k);
|
|
34934
35181
|
const [siteCues, setSiteCues] = (0, react.useState)({});
|
|
34935
|
-
const siteKeys =
|
|
35182
|
+
const siteKeys = screenLines.filter((l) => l.startsWith("site:") && job?.site?.some((s) => s.key === l));
|
|
34936
35183
|
(0, react.useEffect)(() => {
|
|
34937
35184
|
if (!job) return;
|
|
34938
35185
|
for (const k of siteKeys) {
|
|
@@ -35231,7 +35478,7 @@ function LiveView(props) {
|
|
|
35231
35478
|
},
|
|
35232
35479
|
children: [shown.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35233
35480
|
className: "dshl-muted pad",
|
|
35234
|
-
children:
|
|
35481
|
+
children: emptyLines
|
|
35235
35482
|
}), shown.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35236
35483
|
className: `dshl-line ${c.id === activeId ? "active" : ""} ${c.polished ? "ok" : "raw"}`,
|
|
35237
35484
|
onClick: () => player.current?.seek(c.start),
|
|
@@ -35263,7 +35510,7 @@ function LiveView(props) {
|
|
|
35263
35510
|
children: [
|
|
35264
35511
|
paragraphs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35265
35512
|
className: "dshl-muted pad",
|
|
35266
|
-
children:
|
|
35513
|
+
children: emptyLines
|
|
35267
35514
|
}),
|
|
35268
35515
|
paragraphs.some((p) => p.speaker) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35269
35516
|
className: "dshl-muted small",
|
|
@@ -35301,6 +35548,32 @@ function LiveView(props) {
|
|
|
35301
35548
|
onTerms: setTerms
|
|
35302
35549
|
})
|
|
35303
35550
|
] });
|
|
35551
|
+
const live = !!job && job.status === "running" && !job.vod;
|
|
35552
|
+
const slow = live && mode !== "site" && rate !== null && rate < .95;
|
|
35553
|
+
const behindBy = live && mode !== "site" && subs.state === "running" && cues.length > 0 ? now - subs.at : 0;
|
|
35554
|
+
const engineNow = providers.find((x) => x.id === usableEngine());
|
|
35555
|
+
const liveNote = siteOnly && job && job.status !== "resolving" && !job.site?.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35556
|
+
className: "dshl-lag",
|
|
35557
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("site.none") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35558
|
+
className: "dshl-link",
|
|
35559
|
+
onClick: () => changeMode("polished"),
|
|
35560
|
+
children: t("site.switch")
|
|
35561
|
+
})]
|
|
35562
|
+
}) : slow ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35563
|
+
className: "dshl-lag warn",
|
|
35564
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(engineNow && !engineNow.online ? "lag.slowLocal" : "lag.slow").replace("{x}", rate.toFixed(2)) }), engineNow && !engineNow.online && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35565
|
+
className: "dshl-link",
|
|
35566
|
+
onClick: () => setSettingsOpen(true),
|
|
35567
|
+
children: t("lag.settings")
|
|
35568
|
+
})]
|
|
35569
|
+
}) : behindBy > 6 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35570
|
+
className: "dshl-lag",
|
|
35571
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("lag.behind").replace("{t}", CLOCK(behindBy)) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35572
|
+
className: "dshl-link",
|
|
35573
|
+
onClick: () => player.current?.seek(Math.max(0, subs.at - 2)),
|
|
35574
|
+
children: t("lag.wait")
|
|
35575
|
+
})]
|
|
35576
|
+
}) : null;
|
|
35304
35577
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35305
35578
|
className: `dshl ${theater && !activeBench ? "theater" : ""} ${pageMode ? "page" : ""} ${activeBench ? "bench" : ""}`,
|
|
35306
35579
|
"data-dsh-own-drop": "dsh-vidi",
|
|
@@ -35383,7 +35656,7 @@ function LiveView(props) {
|
|
|
35383
35656
|
engine: engine || "local",
|
|
35384
35657
|
onEngine: setEngine,
|
|
35385
35658
|
mode,
|
|
35386
|
-
onMode:
|
|
35659
|
+
onMode: changeMode,
|
|
35387
35660
|
spoken,
|
|
35388
35661
|
onSpoken: setSpoken,
|
|
35389
35662
|
langOptions,
|
|
@@ -35456,7 +35729,7 @@ function LiveView(props) {
|
|
|
35456
35729
|
siteCues: shownSite,
|
|
35457
35730
|
spoken: spokenBase,
|
|
35458
35731
|
style: subStyle,
|
|
35459
|
-
delay: job.mode === "polished"
|
|
35732
|
+
delay: job.vod ? delay : mode === "polished" ? Math.max(delay, 75) : mode === "site" ? 3 : delay,
|
|
35460
35733
|
onDelay: setDelay,
|
|
35461
35734
|
showDelay: job.status === "running" && !job.vod,
|
|
35462
35735
|
onSnapshot: saveSnapshot,
|
|
@@ -35567,9 +35840,17 @@ function LiveView(props) {
|
|
|
35567
35840
|
className: "dshl-btn small",
|
|
35568
35841
|
onClick: stop,
|
|
35569
35842
|
children: t("in.stop")
|
|
35843
|
+
}),
|
|
35844
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35845
|
+
className: "dshl-btn small icon quiet",
|
|
35846
|
+
title: t("in.close"),
|
|
35847
|
+
"aria-label": t("in.close"),
|
|
35848
|
+
onClick: () => void closeVideo(),
|
|
35849
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.close, {})
|
|
35570
35850
|
})
|
|
35571
35851
|
]
|
|
35572
35852
|
}),
|
|
35853
|
+
liveNote,
|
|
35573
35854
|
pageMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
35574
35855
|
className: "dshl-under",
|
|
35575
35856
|
children: transcriptBox
|
|
@@ -35632,6 +35913,10 @@ function SubsButton(props) {
|
|
|
35632
35913
|
className: "now",
|
|
35633
35914
|
children: pct !== void 0 ? t("subs.progress").replace("{p}", String(pct)) : t("subs.progressLive").replace("{t}", CLOCK(at))
|
|
35634
35915
|
}),
|
|
35916
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35917
|
+
className: "short",
|
|
35918
|
+
children: pct !== void 0 ? `${pct}%` : CLOCK(at)
|
|
35919
|
+
}),
|
|
35635
35920
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35636
35921
|
className: "act",
|
|
35637
35922
|
children: t("subs.pause")
|
|
@@ -35665,7 +35950,7 @@ function SubsStarter(props) {
|
|
|
35665
35950
|
(0, react.useEffect)(() => {
|
|
35666
35951
|
if (!open) return;
|
|
35667
35952
|
const close = (e) => {
|
|
35668
|
-
if (!box.current?.contains(e.target) && !pop.current?.contains(e.target)) setOpen(false);
|
|
35953
|
+
if (!box.current?.contains(e.target) && !pop.current?.contains(e.target) && !e.target.closest?.(".dshl-options")) setOpen(false);
|
|
35669
35954
|
};
|
|
35670
35955
|
window.addEventListener("mousedown", close);
|
|
35671
35956
|
return () => window.removeEventListener("mousedown", close);
|
|
@@ -36221,8 +36506,17 @@ const en = {
|
|
|
36221
36506
|
"in.remove": "Remove",
|
|
36222
36507
|
"mode.realtime": "Real-time",
|
|
36223
36508
|
"mode.polished": "Polished",
|
|
36224
|
-
"mode.
|
|
36225
|
-
"mode.
|
|
36509
|
+
"mode.site": "No delay",
|
|
36510
|
+
"mode.site.tip": "The site’s own subtitles only: nothing is transcribed or translated, the picture is not held back",
|
|
36511
|
+
"mode.realtime.tip": "A few seconds behind: transcribed and translated as it is heard",
|
|
36512
|
+
"mode.polished.tip": "1–2 minutes behind: misheard words fixed with context and the picture — the most accurate (default)",
|
|
36513
|
+
"site.none": "This video has no subtitles of its own.",
|
|
36514
|
+
"site.switch": "Switch to Polished",
|
|
36515
|
+
"lag.behind": "Subtitles are {t} behind the picture.",
|
|
36516
|
+
"lag.wait": "Wait for them",
|
|
36517
|
+
"lag.slow": "Transcription runs slower than the stream ({x}×), so subtitles fall further behind.",
|
|
36518
|
+
"lag.slowLocal": "This computer transcribes slower than the stream ({x}×), so subtitles fall further behind. An online service keeps up.",
|
|
36519
|
+
"lag.settings": "Choose one",
|
|
36226
36520
|
"in.uploading": "Uploading {p}%",
|
|
36227
36521
|
"in.drop": "Drop to start",
|
|
36228
36522
|
"empty.title": "Subtitles for any video",
|
|
@@ -36359,6 +36653,8 @@ const en = {
|
|
|
36359
36653
|
"tr.copyShort": "Copy",
|
|
36360
36654
|
"tr.copied": "Copied",
|
|
36361
36655
|
"tr.empty": "Subtitles show up here.",
|
|
36656
|
+
"in.close": "Close this video (its files stay in the video folder)",
|
|
36657
|
+
"tr.listening": "Listening — no speech yet (music or a break so far). Heard up to {t}.",
|
|
36362
36658
|
"gl.new": "New term",
|
|
36363
36659
|
"gl.add": "Add",
|
|
36364
36660
|
"gl.auto": "auto",
|
|
@@ -36545,8 +36841,17 @@ const zh = {
|
|
|
36545
36841
|
"in.remove": "移除",
|
|
36546
36842
|
"mode.realtime": "实时",
|
|
36547
36843
|
"mode.polished": "精修",
|
|
36548
|
-
"mode.
|
|
36549
|
-
"mode.
|
|
36844
|
+
"mode.site": "无延迟",
|
|
36845
|
+
"mode.site.tip": "只看网站自带的字幕:不转写、不翻译,画面也不往后拖",
|
|
36846
|
+
"mode.realtime.tip": "晚几秒:听到就转写、翻译",
|
|
36847
|
+
"mode.polished.tip": "晚 1–2 分钟:结合上下文和画面改正听错的词,字幕最准(默认)",
|
|
36848
|
+
"site.none": "这个视频没有网站自带的字幕。",
|
|
36849
|
+
"site.switch": "换成精修",
|
|
36850
|
+
"lag.behind": "字幕比画面慢了 {t}。",
|
|
36851
|
+
"lag.wait": "等等字幕",
|
|
36852
|
+
"lag.slow": "转写比直播慢({x} 倍速),字幕会越落越远。",
|
|
36853
|
+
"lag.slowLocal": "本机转写比直播慢({x} 倍速),字幕会越落越远。换成在线转写就能跟上。",
|
|
36854
|
+
"lag.settings": "去选一个",
|
|
36550
36855
|
"in.uploading": "上传中 {p}%",
|
|
36551
36856
|
"in.drop": "松手开始",
|
|
36552
36857
|
"empty.title": "给任何视频配字幕",
|
|
@@ -36683,6 +36988,8 @@ const zh = {
|
|
|
36683
36988
|
"tr.copyShort": "复制",
|
|
36684
36989
|
"tr.copied": "已复制",
|
|
36685
36990
|
"tr.empty": "字幕会出现在这里",
|
|
36991
|
+
"in.close": "关闭这个视频(文件还留在视频目录里)",
|
|
36992
|
+
"tr.listening": "正在听,还没有人说话(目前是音乐或插播)。已听到 {t}。",
|
|
36686
36993
|
"gl.new": "新词条",
|
|
36687
36994
|
"gl.add": "添加",
|
|
36688
36995
|
"gl.auto": "自动",
|
|
@@ -36816,6 +37123,7 @@ const CSS = `
|
|
|
36816
37123
|
.dshl-btn.primary:hover:not(:disabled) { background: var(--accent); filter: brightness(1.06); }
|
|
36817
37124
|
.dshl-btn.stop { background: var(--live); border-color: var(--live); color: #fff; }
|
|
36818
37125
|
.dshl-btn.icon { width: 40px; padding: 0; }
|
|
37126
|
+
.dshl-btn.small.icon { width: 30px; }
|
|
36819
37127
|
.dshl-btn.icon.back { color: var(--muted); }
|
|
36820
37128
|
.dshl-btn.icon.back svg { transform: rotate(180deg); }
|
|
36821
37129
|
.dshl-btn.block { display: flex; width: 100%; }
|
|
@@ -36843,10 +37151,25 @@ const CSS = `
|
|
|
36843
37151
|
.dshl-switch.on { background: var(--accent); }
|
|
36844
37152
|
.dshl-switch.on i { left: 16px; }
|
|
36845
37153
|
|
|
36846
|
-
.dshl
|
|
36847
|
-
|
|
36848
|
-
|
|
36849
|
-
.dshl
|
|
37154
|
+
.dshl-select { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; height: 32px; padding: 0 10px; border: 1px solid var(--line); border-radius: 8px;
|
|
37155
|
+
background: var(--bg); color: var(--ink); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
|
|
37156
|
+
.dshl-select .v { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
37157
|
+
.dshl-select .caret { flex: none; width: 6px; height: 6px; margin: -3px 1px 0 0; border-right: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint); transform: rotate(45deg); transition: transform .15s; }
|
|
37158
|
+
.dshl-select.open .caret { margin-top: 3px; transform: rotate(-135deg); }
|
|
37159
|
+
.dshl-select:hover:not(:disabled), .dshl-select.open { border-color: color-mix(in srgb, var(--ink) 25%, var(--line)); }
|
|
37160
|
+
.dshl-select:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 1px; }
|
|
37161
|
+
.dshl-select:disabled { opacity: .5; cursor: default; }
|
|
37162
|
+
/* The list: drawn at the page's top level (so no panel clips it), in the colours of where it opened. */
|
|
37163
|
+
.dshl-options { position: fixed; z-index: 2147483000; overflow: auto; overscroll-behavior: contain; padding: 4px; border: 1px solid var(--line, #e4e3de); border-radius: 10px;
|
|
37164
|
+
background: var(--bg, #fff); color: var(--ink, #1b1b19); box-shadow: 0 10px 30px rgba(0,0,0,.18), 0 2px 6px rgba(0,0,0,.08); font-size: 13px; line-height: 1.4; -webkit-font-smoothing: antialiased; }
|
|
37165
|
+
.dshl-options .grp { padding: 8px 10px 4px; color: var(--faint, #9a9993); font-size: 11.5px; font-weight: 600; }
|
|
37166
|
+
.dshl-options .opt { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 6px 10px; border-radius: 6px; cursor: pointer; }
|
|
37167
|
+
.dshl-options .opt.act { background: var(--hover, rgba(0,0,0,.05)); }
|
|
37168
|
+
.dshl-options .opt.on { font-weight: 550; }
|
|
37169
|
+
.dshl-options .opt.off { opacity: .45; cursor: default; }
|
|
37170
|
+
.dshl-options .opt .l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
37171
|
+
.dshl-options .opt .h { flex: none; color: var(--faint, #9a9993); font-size: 12px; }
|
|
37172
|
+
.dshl-options .opt .ok { flex: none; margin-left: auto; color: var(--accent, #3b6ef5); }
|
|
36850
37173
|
|
|
36851
37174
|
/* Toggle chips (several can be on): languages to show or download */
|
|
36852
37175
|
.dshl-chips .row, .dshl-checks2 { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
@@ -36857,7 +37180,8 @@ const CSS = `
|
|
|
36857
37180
|
.dshl-chips .chip i { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-left: -3px; border-radius: 50%;
|
|
36858
37181
|
background: var(--accent); color: #fff; font-size: 10.5px; font-style: normal; font-weight: 600; }
|
|
36859
37182
|
.dshl-chips .chip.site:not(.on) { border-style: dashed; }
|
|
36860
|
-
.dshl-chips select.chip.add {
|
|
37183
|
+
.dshl-chips .dshl-select.chip.add { height: 28px; border-style: dashed; padding: 0 10px; background: transparent; }
|
|
37184
|
+
.dshl-chips .dshl-select.chip.add .caret { display: none; }
|
|
36861
37185
|
.dshl-chips .chip.ghost { width: 28px; padding: 0; justify-content: center; }
|
|
36862
37186
|
.dshl-checks2 { margin: 0 0 10px; }
|
|
36863
37187
|
.dshl-checks2 button i { width: 14px; height: 14px; margin-left: -2px; border: 1.5px solid currentColor; border-radius: 4px; opacity: .55; flex: none; }
|
|
@@ -36941,7 +37265,7 @@ html:not([data-platform]) [data-sidebar-drawer] .dshl-panelwrap .dshl.page .dshl
|
|
|
36941
37265
|
every state, full screen included; the bar's 40px row is centred on the same line. */
|
|
36942
37266
|
html[data-platform='darwin'] .dshl-panelwrap .dshl.page .dshl-top { padding-top: 5px; padding-bottom: 8px; }
|
|
36943
37267
|
html[data-platform='darwin'] .dshl.page .dshl-top { -webkit-app-region: drag; app-region: drag; }
|
|
36944
|
-
html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, select, .dshl-pick) { -webkit-app-region: no-drag; app-region: no-drag; }
|
|
37268
|
+
html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, .dshl-select, .dshl-pick) { -webkit-app-region: no-drag; app-region: no-drag; }
|
|
36945
37269
|
.dshl.page { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
|
36946
37270
|
.dshl.page .dshl-main { flex: 1; min-height: 0; grid-template-columns: minmax(0, 1fr) clamp(320px, 30vw, 420px); align-items: stretch; padding-bottom: 16px; }
|
|
36947
37271
|
/* Video page: player on top, then the status row, then subtitles/glossary filling what is left; chat on the right. */
|
|
@@ -36951,6 +37275,8 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
36951
37275
|
.dshl.page .dshl-side { position: static; height: 100%; max-height: none; }
|
|
36952
37276
|
/* Workbench open: the video and its subtitles keep a column on the left; the bench takes the rest. */
|
|
36953
37277
|
.dshl.page.bench .dshl-main { grid-template-columns: clamp(340px, 32vw, 520px) minmax(0, 1fr); }
|
|
37278
|
+
.dshl-lag { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: -2px 0 8px; padding: 7px 12px; border-radius: 8px; background: var(--l1, rgba(0,0,0,.04)); color: var(--muted); font-size: 12.5px; }
|
|
37279
|
+
.dshl-lag.warn { background: color-mix(in srgb, #f5a524 14%, transparent); color: var(--ink); }
|
|
36954
37280
|
.dshl-guard { display: grid; gap: 8px; margin: 12px; padding: 12px 14px; border: 1px solid color-mix(in srgb, #d1434b 35%, var(--line)); border-radius: 10px; color: var(--ink); font-size: 13px; }
|
|
36955
37281
|
.dshl-guard p { margin: 0; line-height: 1.6; }
|
|
36956
37282
|
.dshl-guard code { color: var(--muted); font-size: 12px; white-space: pre-wrap; word-break: break-word; }
|
|
@@ -37050,7 +37376,12 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37050
37376
|
.dshl-ctrlrow .pill svg { flex: none; width: 18px; height: 18px; }
|
|
37051
37377
|
.dshl-ctrlrow .pill .txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
37052
37378
|
.dshl-ctrlrow .pill .caret { flex: none; width: 5px; height: 5px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; opacity: .8; transform: translateY(1px) rotate(-135deg); }
|
|
37053
|
-
@container (max-width: 640px) {
|
|
37379
|
+
@container (max-width: 640px) {
|
|
37380
|
+
.dshl-ctrlrow .pill .txt, .dshl-ctrlrow .pill .caret { display: none; } .dshl-ctrlrow .pill { padding: 0 6px; }
|
|
37381
|
+
/* Narrow player (a workbench beside it, a phone): fewer words, no volume slider (the mute button stays). */
|
|
37382
|
+
.dshl-ctrlrow .vol, .dshl-ctrlrow .theater { display: none; }
|
|
37383
|
+
.dshl-ctrlrow .ic { width: 32px; height: 32px; }
|
|
37384
|
+
}
|
|
37054
37385
|
@container (max-width: 520px) {
|
|
37055
37386
|
.dshl-ctrlrow { gap: 2px; }
|
|
37056
37387
|
.dshl-ctrlrow .vol, .dshl-ctrlrow .mute, .dshl-ctrlrow .theater { display: none; }
|
|
@@ -37066,7 +37397,8 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37066
37397
|
.dshl-subsbtn:hover { filter: brightness(1.08); }
|
|
37067
37398
|
.dshl-subsbtn.running { background: linear-gradient(to right, rgba(127,176,255,.45) var(--p, 0%), rgba(255,255,255,.14) var(--p, 0%)); font-weight: 500; }
|
|
37068
37399
|
.dshl-subsbtn.running .dot { width: 7px; height: 7px; border-radius: 50%; background: #ff5a5f; animation: dshl-pulse 1.6s ease-in-out infinite; }
|
|
37069
|
-
.dshl-subsbtn.running .act { display: none; }
|
|
37400
|
+
.dshl-subsbtn.running .act, .dshl-subsbtn .short { display: none; }
|
|
37401
|
+
@container (max-width: 640px) { .dshl-subsbtn .now { display: none; } .dshl-subsbtn.running:not(:hover) .short { display: inline; } }
|
|
37070
37402
|
.dshl-subsbtn.running:hover .now { display: none; }
|
|
37071
37403
|
.dshl-subsbtn.running:hover .act { display: inline; }
|
|
37072
37404
|
.dshl-subsbtn.running:hover .dot { width: 8px; height: 8px; border-radius: 1px; background: #fff; animation: none; }
|
|
@@ -37119,11 +37451,7 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37119
37451
|
.dshl-submenu { display: grid; gap: 4px; }
|
|
37120
37452
|
.dshl-submenu .slots { display: grid; gap: 8px; padding: 4px 6px 10px; }
|
|
37121
37453
|
.dshl-submenu .slot { display: grid; grid-template-columns: 56px 1fr; align-items: center; gap: 10px; color: var(--muted); }
|
|
37122
|
-
.dshl-submenu .slot select { width: 100%;
|
|
37123
|
-
appearance: none; -webkit-appearance: none;
|
|
37124
|
-
background: var(--l1) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23b4b4ba' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 10px center; }
|
|
37125
|
-
.dshl-submenu .slot select:hover { border-color: rgba(255,255,255,.28); }
|
|
37126
|
-
.dshl-submenu .slot select option, .dshl-submenu .slot select optgroup { background: #fff; color: #111; }
|
|
37454
|
+
.dshl-submenu .slot .dshl-select { width: 100%; height: 34px; background: var(--l1); }
|
|
37127
37455
|
.dshl-submenu .swap { justify-self: start; height: 26px; margin-left: 66px; padding: 0 10px; border: 0; border-radius: 6px; background: var(--l2); color: var(--muted); font-size: 12px; cursor: pointer; }
|
|
37128
37456
|
.dshl-submenu .swap:hover { color: var(--ink); }
|
|
37129
37457
|
.dshl-submenu .opts { position: sticky; bottom: -10px; display: grid; gap: 2px; margin: 0 -8px -10px; padding: 6px 8px 10px; border-top: 1px solid var(--line-soft); background: rgb(28,28,31); }
|
|
@@ -37163,12 +37491,12 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37163
37491
|
.dshl-rs .rs-hint { margin: 0; color: var(--faint); font-size: 12px; line-height: 1.55; }
|
|
37164
37492
|
.dshl-rs .rs-err { margin: 0; color: #ff8f92; font-size: 12px; line-height: 1.55; }
|
|
37165
37493
|
.dshl-rs .rs-field { display: grid; grid-template-columns: 76px 1fr; align-items: center; gap: 10px; font-size: 13px; color: var(--muted); }
|
|
37166
|
-
.dshl-rs .rs-field
|
|
37167
|
-
.dshl-rs .rs-field
|
|
37168
|
-
.dshl-rs .rs-field input:focus
|
|
37494
|
+
.dshl-rs .rs-field .dshl-select { width: 100%; height: 34px; background: var(--l1); }
|
|
37495
|
+
.dshl-rs .rs-field input { width: 100%; height: 34px; padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--l1); color: var(--ink); font: inherit; font-size: 13px; }
|
|
37496
|
+
.dshl-rs .rs-field input:focus { outline: none; border-color: var(--accent); }
|
|
37169
37497
|
.dshl-rs .rs-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
37170
37498
|
.dshl-menu.sheet .dshl-rs .rs-row { min-height: 50px; }
|
|
37171
|
-
.dshl-menu.sheet .dshl-rs .rs-field input, .dshl-menu.sheet .dshl-rs .rs-field select { height: 42px; font-size: 15px; }
|
|
37499
|
+
.dshl-menu.sheet .dshl-rs .rs-field input, .dshl-menu.sheet .dshl-rs .rs-field .dshl-select { height: 42px; font-size: 15px; }
|
|
37172
37500
|
.dshl-menu.sheet .dshl-rs .dshl-btn.block { height: 46px; font-size: 15px; }
|
|
37173
37501
|
.dshl-dl { display: grid; gap: 14px; padding: 0 6px; }
|
|
37174
37502
|
.dshl-dl .grp { display: grid; gap: 8px; }
|
|
@@ -37198,7 +37526,7 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37198
37526
|
.dshl-menu.sheet .dshl-seg button, .dshl-menu.sheet .dshl-submenu .seg button { min-width: 52px; height: 34px; font-size: 14px; }
|
|
37199
37527
|
.dshl-menu.sheet .dshl-submenu .seg { width: 210px; }
|
|
37200
37528
|
.dshl-menu.sheet .dshl-submenu .slot { grid-template-columns: 64px 1fr; font-size: 15px; }
|
|
37201
|
-
.dshl-menu.sheet .dshl-submenu .slot select { height: 44px; font-size: 15px; }
|
|
37529
|
+
.dshl-menu.sheet .dshl-submenu .slot .dshl-select { height: 44px; font-size: 15px; }
|
|
37202
37530
|
.dshl-menu.sheet .dshl-submenu .swap { height: 34px; margin-left: 74px; font-size: 14px; }
|
|
37203
37531
|
.dshl-menu.sheet .dshl-switch { width: 44px; height: 26px; }
|
|
37204
37532
|
.dshl-menu.sheet .dshl-switch i { width: 22px; height: 22px; }
|
|
@@ -37318,7 +37646,7 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37318
37646
|
.dshl .srow { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 46px; font-size: 13.5px; }
|
|
37319
37647
|
.dshl-pop.settings { width: 320px; padding: 6px 12px; }
|
|
37320
37648
|
.dshl-pop.settings .srow + .srow { border-top: 1px solid var(--line-soft); }
|
|
37321
|
-
.dshl-pop.settings .srow > select, .dshl-pop.settings .srow > .dshl-seg { width: 168px; }
|
|
37649
|
+
.dshl-pop.settings .srow > .dshl-select, .dshl-pop.settings .srow > .dshl-seg { width: 168px; }
|
|
37322
37650
|
.dshl-pop.settings .srow > .dshl-seg button { flex: 1; padding: 0 6px; }
|
|
37323
37651
|
.dshl .srow.col { display: grid; justify-content: stretch; gap: 8px; padding: 10px 0; }
|
|
37324
37652
|
.dshl .srow .keyline { display: flex; align-items: center; gap: 6px; }
|
|
@@ -37333,7 +37661,7 @@ html[data-platform='darwin'] .dshl.page .dshl-top :is(button, input, label, a, s
|
|
|
37333
37661
|
.dshl .srow .keyfoot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; color: var(--faint); font-size: 12px; line-height: 1.5; }
|
|
37334
37662
|
.dshl .srow .hint { color: var(--muted); font-size: 12.5px; line-height: 1.6; }
|
|
37335
37663
|
.dshl .srow .pair { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
37336
|
-
.dshl .srow .pair > select { width: 168px; }
|
|
37664
|
+
.dshl .srow .pair > .dshl-select { width: 168px; }
|
|
37337
37665
|
.dshl-pop.usage { min-width: 320px; padding: 12px 14px; }
|
|
37338
37666
|
.dshl-pop.usage h3 { margin: 0 0 8px; color: var(--muted); font-size: 12.5px; font-weight: 600; }
|
|
37339
37667
|
.dshl-pop.usage table { width: 100%; border-collapse: collapse; font-size: 12.5px; font-variant-numeric: tabular-nums; }
|
|
@@ -37429,12 +37757,12 @@ const samePath = (a, b) => !!a && !!b && a.replace(/\/+$/, "") === b.replace(/\/
|
|
|
37429
37757
|
* It lives in a "Video" workspace on the video folder, so DeepSeek's own file
|
|
37430
37758
|
* and shell tools can work on the recordings, clips and frames directly.
|
|
37431
37759
|
*/
|
|
37432
|
-
async function pageSession(sessions, workspaces, title) {
|
|
37760
|
+
async function pageSession(sessions, workspaces, title, fresh = false) {
|
|
37433
37761
|
let saved = null;
|
|
37434
37762
|
try {
|
|
37435
37763
|
saved = localStorage.getItem(PAGE_SESSION);
|
|
37436
37764
|
} catch {}
|
|
37437
|
-
if (saved && sessions.has(saved)) return saved;
|
|
37765
|
+
if (!fresh && saved && sessions.has(saved)) return saved;
|
|
37438
37766
|
const deps = await fetch("/live/api/deps").then((r) => r.json());
|
|
37439
37767
|
if (!deps?.dataDir) throw new Error("the video plugin is not running yet");
|
|
37440
37768
|
const known = () => workspaces.list?.getSnapshot().items?.find((w) => samePath(w.path, deps.dataDir));
|
|
@@ -37498,9 +37826,48 @@ function closePage(sessions) {
|
|
|
37498
37826
|
});
|
|
37499
37827
|
if (returnTo && sessions.has(returnTo)) sessions.open(returnTo);
|
|
37500
37828
|
}
|
|
37829
|
+
/** The video folder (the "Video" workspace), asked once. */
|
|
37830
|
+
let dataDirP;
|
|
37831
|
+
const videoFolder = () => dataDirP ??= fetch("/live/api/deps").then((r) => r.json()).then((d) => d?.dataDir).catch(() => {
|
|
37832
|
+
dataDirP = void 0;
|
|
37833
|
+
});
|
|
37834
|
+
/**
|
|
37835
|
+
* Each video gets a conversation of its own, named after it ("▶ title"), so
|
|
37836
|
+
* the history lists videos rather than a row of "Untitled". This applies to
|
|
37837
|
+
* the conversations in the video folder; a chat elsewhere that opened a video
|
|
37838
|
+
* keeps its own name and switches video in place.
|
|
37839
|
+
*/
|
|
37840
|
+
function videoHandlers(sessions, workspaces, sessionId, fallbackTitle) {
|
|
37841
|
+
const own = async () => {
|
|
37842
|
+
const cwd = sessions.info?.(sessionId)?.cwd;
|
|
37843
|
+
const dir = await videoFolder();
|
|
37844
|
+
return !!cwd && !!dir && samePath(cwd, dir);
|
|
37845
|
+
};
|
|
37846
|
+
return {
|
|
37847
|
+
onNewVideo: async (link) => {
|
|
37848
|
+
if (!workspaces || !await own()) return false;
|
|
37849
|
+
try {
|
|
37850
|
+
await openPage(sessions, {
|
|
37851
|
+
sessionId: await pageSession(sessions, workspaces, fallbackTitle, true),
|
|
37852
|
+
pending: link,
|
|
37853
|
+
workspaces
|
|
37854
|
+
});
|
|
37855
|
+
return true;
|
|
37856
|
+
} catch {
|
|
37857
|
+
return false;
|
|
37858
|
+
}
|
|
37859
|
+
},
|
|
37860
|
+
onTitle: (title) => {
|
|
37861
|
+
own().then((yes) => {
|
|
37862
|
+
const now = sessions.info?.(sessionId)?.title;
|
|
37863
|
+
if (yes && sessions.rename && (!now || now.startsWith("▶ "))) sessions.rename(sessionId, `▶ ${title}`.slice(0, 80));
|
|
37864
|
+
});
|
|
37865
|
+
}
|
|
37866
|
+
};
|
|
37867
|
+
}
|
|
37501
37868
|
/** The video page as a main panel (newer DS-Harness): the app's sidebar and its toggle stay. */
|
|
37502
37869
|
function VideoPanel(props) {
|
|
37503
|
-
const { sessions, t } = props;
|
|
37870
|
+
const { sessions, workspaces, t } = props;
|
|
37504
37871
|
const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.get);
|
|
37505
37872
|
const face = (0, react.useSyncExternalStore)(sessions.subscribe, () => page.sessionId ? sessions.face(page.sessionId) : void 0);
|
|
37506
37873
|
const chat = (0, react.useSyncExternalStore)(sessions.subscribe, () => page.sessionId ? sessions.chat(page.sessionId) : void 0);
|
|
@@ -37513,6 +37880,7 @@ function VideoPanel(props) {
|
|
|
37513
37880
|
page: true,
|
|
37514
37881
|
chatFace: face,
|
|
37515
37882
|
chatSource: chat,
|
|
37883
|
+
...videoHandlers(sessions, workspaces, page.sessionId, t("view.live")),
|
|
37516
37884
|
pending: page.pending,
|
|
37517
37885
|
onPendingDone: () => pageStore.set({ pending: void 0 })
|
|
37518
37886
|
}, page.sessionId)
|
|
@@ -37520,7 +37888,7 @@ function VideoPanel(props) {
|
|
|
37520
37888
|
}
|
|
37521
37889
|
/** Full-window video page (shell.overlay, older DS-Harness): player, subtitles, terms and the chat with DeepSeek. */
|
|
37522
37890
|
function VideoPage(props) {
|
|
37523
|
-
const { sessions, t } = props;
|
|
37891
|
+
const { sessions, workspaces, t } = props;
|
|
37524
37892
|
const page = (0, react.useSyncExternalStore)(pageStore.subscribe, pageStore.get);
|
|
37525
37893
|
const face = (0, react.useSyncExternalStore)(sessions.subscribe, () => page.sessionId ? sessions.face(page.sessionId) : void 0);
|
|
37526
37894
|
const chat = (0, react.useSyncExternalStore)(sessions.subscribe, () => page.sessionId ? sessions.chat(page.sessionId) : void 0);
|
|
@@ -37537,6 +37905,7 @@ function VideoPage(props) {
|
|
|
37537
37905
|
t,
|
|
37538
37906
|
chatFace: face,
|
|
37539
37907
|
chatSource: chat,
|
|
37908
|
+
...videoHandlers(sessions, workspaces, page.sessionId, t("view.live")),
|
|
37540
37909
|
onClose: () => closePage(sessions),
|
|
37541
37910
|
pending: page.pending,
|
|
37542
37911
|
onPendingDone: () => pageStore.set({ pending: void 0 })
|
|
@@ -37669,7 +38038,19 @@ function sessionsOf(ctx, extra) {
|
|
|
37669
38038
|
return;
|
|
37670
38039
|
}
|
|
37671
38040
|
},
|
|
37672
|
-
subscribe: (fn) => s.list.subscribe(fn)
|
|
38041
|
+
subscribe: (fn) => s.list.subscribe(fn),
|
|
38042
|
+
rename: (id, title) => {
|
|
38043
|
+
try {
|
|
38044
|
+
s.binding(id)?.session?.rename?.(title);
|
|
38045
|
+
} catch {}
|
|
38046
|
+
},
|
|
38047
|
+
info: (id) => {
|
|
38048
|
+
const x = list().byId?.[id];
|
|
38049
|
+
return x ? {
|
|
38050
|
+
title: x.title,
|
|
38051
|
+
cwd: x.cwd
|
|
38052
|
+
} : void 0;
|
|
38053
|
+
}
|
|
37673
38054
|
};
|
|
37674
38055
|
}
|
|
37675
38056
|
function apply(ctx) {
|
|
@@ -37728,6 +38109,7 @@ function apply(ctx) {
|
|
|
37728
38109
|
label: () => t("page.open"),
|
|
37729
38110
|
inject: () => ({
|
|
37730
38111
|
sessions: sessions(),
|
|
38112
|
+
workspaces: ctx.workspaces,
|
|
37731
38113
|
t
|
|
37732
38114
|
})
|
|
37733
38115
|
}, VideoPage));
|
|
@@ -37745,6 +38127,7 @@ function apply(ctx) {
|
|
|
37745
38127
|
locale: NS,
|
|
37746
38128
|
inject: () => ({
|
|
37747
38129
|
sessions: sessions(),
|
|
38130
|
+
workspaces: ctx.workspaces,
|
|
37748
38131
|
t
|
|
37749
38132
|
})
|
|
37750
38133
|
}, VideoPanel));
|