dsh-vidi 0.2.3 → 0.3.1
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 +85 -14
- package/lib/index.mjs +20 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -33858,6 +33858,43 @@ const Player = (0, react.forwardRef)(function Player(props, ref) {
|
|
|
33858
33858
|
});
|
|
33859
33859
|
});
|
|
33860
33860
|
|
|
33861
|
+
// src/client/extensions.ts
|
|
33862
|
+
function registry() {
|
|
33863
|
+
let items = [];
|
|
33864
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
33865
|
+
const emit = () => {
|
|
33866
|
+
for (const l of listeners) l();
|
|
33867
|
+
};
|
|
33868
|
+
return {
|
|
33869
|
+
get: () => items,
|
|
33870
|
+
subscribe(l) {
|
|
33871
|
+
listeners.add(l);
|
|
33872
|
+
return () => {
|
|
33873
|
+
listeners.delete(l);
|
|
33874
|
+
};
|
|
33875
|
+
},
|
|
33876
|
+
/** Add one; returns the function that removes it. */
|
|
33877
|
+
add(item) {
|
|
33878
|
+
items = [...items.filter((x) => x.id !== item.id), item].sort((a, b) => (a.order ?? 50) - (b.order ?? 50));
|
|
33879
|
+
emit();
|
|
33880
|
+
return () => {
|
|
33881
|
+
items = items.filter((x) => x !== item);
|
|
33882
|
+
emit();
|
|
33883
|
+
};
|
|
33884
|
+
}
|
|
33885
|
+
};
|
|
33886
|
+
}
|
|
33887
|
+
/**
|
|
33888
|
+
* Extension points of the video page, for companion plugins (inject
|
|
33889
|
+
* 'vidiUi'): extra tabs under the player and extra player menus. The page
|
|
33890
|
+
* itself stays in this package; companions only add.
|
|
33891
|
+
*/
|
|
33892
|
+
const vidiUi = {
|
|
33893
|
+
version: 1,
|
|
33894
|
+
panels: registry(),
|
|
33895
|
+
playerMenus: registry()
|
|
33896
|
+
};
|
|
33897
|
+
|
|
33861
33898
|
// src/client/RestreamPanel.tsx
|
|
33862
33899
|
/** Platforms, in the order people in China most often push to; YouTube and Twitch have one fixed ingest address. */
|
|
33863
33900
|
const PLATFORMS = [
|
|
@@ -33937,7 +33974,7 @@ function RestreamPanel(props) {
|
|
|
33937
33974
|
return () => clearInterval(id);
|
|
33938
33975
|
}, [running]);
|
|
33939
33976
|
/** Known notes from the host, in the page's language. */
|
|
33940
|
-
const note = (m) => m === "ended" ? t("rs.m.ended") : m === "no-burn" ? t("rs.m.noBurn") : m;
|
|
33977
|
+
const note = (m) => m === "ended" ? t("rs.m.ended") : m === "no-burn" ? t("rs.m.noBurn") : m === "stalled" ? t("rs.m.stalled") : m;
|
|
33941
33978
|
const pname = (p) => t(`rs.p.${p}`);
|
|
33942
33979
|
const save = async () => {
|
|
33943
33980
|
if (!editing) return;
|
|
@@ -35073,24 +35110,45 @@ function LiveView(props) {
|
|
|
35073
35110
|
})] });
|
|
35074
35111
|
/** On the video page the tools share the tab row, leaving the height to the lines. */
|
|
35075
35112
|
const inline = pageMode && tab === "transcript" && !!job;
|
|
35113
|
+
const extPanels = (0, react.useSyncExternalStore)(vidiUi.panels.subscribe, vidiUi.panels.get);
|
|
35114
|
+
const extMenus = (0, react.useSyncExternalStore)(vidiUi.playerMenus.subscribe, vidiUi.playerMenus.get);
|
|
35115
|
+
const view = job ? {
|
|
35116
|
+
job,
|
|
35117
|
+
cues,
|
|
35118
|
+
sessionId,
|
|
35119
|
+
now,
|
|
35120
|
+
seek: (s) => player.current?.seek(s),
|
|
35121
|
+
style: subStyle,
|
|
35122
|
+
lines: shownLines,
|
|
35123
|
+
ui
|
|
35124
|
+
} : void 0;
|
|
35076
35125
|
/** Subtitle lines and glossary: under the player on the video page, beside it in a conversation. */
|
|
35077
35126
|
const transcriptBox = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
35078
35127
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35079
35128
|
className: "dshl-sidetabs",
|
|
35080
35129
|
role: "tablist",
|
|
35081
|
-
children: [
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35130
|
+
children: [
|
|
35131
|
+
["transcript", "glossary"].map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35132
|
+
role: "tab",
|
|
35133
|
+
className: tab === k ? "on" : "",
|
|
35134
|
+
onClick: () => setTab(k),
|
|
35135
|
+
children: t(`tab.${k}`)
|
|
35136
|
+
}, k)),
|
|
35137
|
+
job && extPanels.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35138
|
+
role: "tab",
|
|
35139
|
+
className: tab === p.id ? "on" : "",
|
|
35140
|
+
onClick: () => setTab(p.id),
|
|
35141
|
+
children: p.label(ui)
|
|
35142
|
+
}, p.id)),
|
|
35143
|
+
inline && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35144
|
+
className: "dshl-tools inline",
|
|
35145
|
+
children: [
|
|
35146
|
+
trMode === "lines" && trSearch,
|
|
35147
|
+
trSeg,
|
|
35148
|
+
trAction
|
|
35149
|
+
]
|
|
35150
|
+
})
|
|
35151
|
+
]
|
|
35094
35152
|
}),
|
|
35095
35153
|
!job && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35096
35154
|
className: "dshl-panel dshl-muted",
|
|
@@ -35172,6 +35230,10 @@ function LiveView(props) {
|
|
|
35172
35230
|
})
|
|
35173
35231
|
]
|
|
35174
35232
|
}),
|
|
35233
|
+
job && view && extPanels.filter((p) => p.id === tab).map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35234
|
+
className: "dshl-panel dshl-ext",
|
|
35235
|
+
children: p.render(view)
|
|
35236
|
+
}, p.id)),
|
|
35175
35237
|
tab === "glossary" && job && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlossaryPanel, {
|
|
35176
35238
|
jobId: job.id,
|
|
35177
35239
|
terms,
|
|
@@ -35408,6 +35470,12 @@ function LiveView(props) {
|
|
|
35408
35470
|
t
|
|
35409
35471
|
})
|
|
35410
35472
|
},
|
|
35473
|
+
...extMenus.map((m) => ({
|
|
35474
|
+
id: `ext:${m.id}`,
|
|
35475
|
+
icon: m.icon,
|
|
35476
|
+
title: m.title(ui),
|
|
35477
|
+
content: view ? m.render(view, () => {}) : null
|
|
35478
|
+
})),
|
|
35411
35479
|
{
|
|
35412
35480
|
id: "download",
|
|
35413
35481
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon.download, {}),
|
|
@@ -36023,6 +36091,7 @@ const en = {
|
|
|
36023
36091
|
"rs.stopOne": "Stop this one",
|
|
36024
36092
|
"rs.join": "Add",
|
|
36025
36093
|
"rs.m.ended": "The video ended, so restreaming stopped.",
|
|
36094
|
+
"rs.m.stalled": "Nothing more to send (the recording stopped), so restreaming stopped.",
|
|
36026
36095
|
"rs.m.noBurn": "Subtitles could not be drawn on this computer; streaming without them.",
|
|
36027
36096
|
"rs.note": "Starts from where you are watching, with the subtitles you see. Keys stay on this computer.",
|
|
36028
36097
|
"rs.s.connecting": "Connecting",
|
|
@@ -36338,6 +36407,7 @@ const zh = {
|
|
|
36338
36407
|
"rs.stopOne": "停止这一路",
|
|
36339
36408
|
"rs.join": "加入",
|
|
36340
36409
|
"rs.m.ended": "视频播完了,转播已停止。",
|
|
36410
|
+
"rs.m.stalled": "没有新画面可推了(录制已停),转播已停止。",
|
|
36341
36411
|
"rs.m.noBurn": "这台电脑画不了字幕,先不带字幕推流。",
|
|
36342
36412
|
"rs.note": "从你正在看的位置开始,带着你看到的字幕。推流码只存在这台电脑上。",
|
|
36343
36413
|
"rs.s.connecting": "连接中",
|
|
@@ -37432,6 +37502,7 @@ function apply(ctx) {
|
|
|
37432
37502
|
zh,
|
|
37433
37503
|
en
|
|
37434
37504
|
}), "dsh-vidi: dictionaries");
|
|
37505
|
+
ctx.effect(() => ctx.provide("vidiUi", vidiUi), "dsh-vidi: page extensions");
|
|
37435
37506
|
const t = ctx.locale.bind(NS);
|
|
37436
37507
|
const extra = {};
|
|
37437
37508
|
ctx.inject(["uiWorkspace"], (c) => {
|
package/lib/index.mjs
CHANGED
|
@@ -1272,6 +1272,9 @@ var Restream = class extends EventEmitter {
|
|
|
1272
1272
|
blank;
|
|
1273
1273
|
startAt = 0;
|
|
1274
1274
|
spawnedAt = 0;
|
|
1275
|
+
watchdog;
|
|
1276
|
+
lastOut = 0;
|
|
1277
|
+
stuckSince = Date.now();
|
|
1275
1278
|
constructor(source, opts, targets, log) {
|
|
1276
1279
|
super();
|
|
1277
1280
|
this.source = source;
|
|
@@ -1415,6 +1418,18 @@ var Restream = class extends EventEmitter {
|
|
|
1415
1418
|
});
|
|
1416
1419
|
if (burn) this.feedSubtitles(w, h);
|
|
1417
1420
|
for (const p of this.pushes.values()) this.connect(p.target.id);
|
|
1421
|
+
this.watchdog = setInterval(() => {
|
|
1422
|
+
if (this.stopped) return;
|
|
1423
|
+
if (this.outTime > this.lastOut) {
|
|
1424
|
+
this.lastOut = this.outTime;
|
|
1425
|
+
this.stuckSince = Date.now();
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (Date.now() - this.stuckSince > 25e3) {
|
|
1429
|
+
this.log("restream: the encoder stopped producing");
|
|
1430
|
+
this.stop("stalled");
|
|
1431
|
+
}
|
|
1432
|
+
}, 5e3);
|
|
1418
1433
|
this.changed();
|
|
1419
1434
|
}
|
|
1420
1435
|
/** Subtitle pictures for media times from the start on, kept about a second ahead of the encoder. */
|
|
@@ -1562,6 +1577,7 @@ var Restream = class extends EventEmitter {
|
|
|
1562
1577
|
async stop(message) {
|
|
1563
1578
|
if (this.stopped) return;
|
|
1564
1579
|
this.stopped = true;
|
|
1580
|
+
clearInterval(this.watchdog);
|
|
1565
1581
|
process.off("exit", this.killAll);
|
|
1566
1582
|
clearInterval(this.subsTimer);
|
|
1567
1583
|
for (const p of this.pushes.values()) {
|
|
@@ -2409,6 +2425,10 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
2409
2425
|
return;
|
|
2410
2426
|
}
|
|
2411
2427
|
if (!this.segments().length) throw new Error("nothing recorded yet");
|
|
2428
|
+
if (this.vod && this.duration && opts.from >= this.duration - 1.5) opts = {
|
|
2429
|
+
...opts,
|
|
2430
|
+
from: 0
|
|
2431
|
+
};
|
|
2412
2432
|
const r = new Restream({
|
|
2413
2433
|
input: (from) => this.restreamInput(from),
|
|
2414
2434
|
rowsAt: (t) => this.rowsAt(t, opts.style)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vidi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A video tool for DeepSeek Harness: watch live streams, videos and podcasts with real-time subtitles in any language, then clip and download",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|