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 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: [["transcript", "glossary"].map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35082
- role: "tab",
35083
- className: tab === k ? "on" : "",
35084
- onClick: () => setTab(k),
35085
- children: t(`tab.${k}`)
35086
- }, k)), inline && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35087
- className: "dshl-tools inline",
35088
- children: [
35089
- trMode === "lines" && trSearch,
35090
- trSeg,
35091
- trAction
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.2.3",
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",