dsh-vidi 0.8.2 → 0.8.4
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 +95 -28
- package/lib/index.mjs +121 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -35281,19 +35281,6 @@ function useDevices(enabled) {
|
|
|
35281
35281
|
}, [enabled, devices]);
|
|
35282
35282
|
return devices;
|
|
35283
35283
|
}
|
|
35284
|
-
/**
|
|
35285
|
-
* Ask for the camera / microphone from the page first when the page runs on the same computer: the system
|
|
35286
|
-
* then asks the user once for this app, and the capture that follows is allowed.
|
|
35287
|
-
*/
|
|
35288
|
-
async function askDeviceAccess(video) {
|
|
35289
|
-
if (!(location.protocol === "dsh-app:" || /^(localhost|127\.0\.0\.1|\[::1\])$/.test(location.hostname)) || !navigator.mediaDevices?.getUserMedia) return;
|
|
35290
|
-
try {
|
|
35291
|
-
(await navigator.mediaDevices.getUserMedia({
|
|
35292
|
-
audio: true,
|
|
35293
|
-
video
|
|
35294
|
-
})).getTracks().forEach((tr) => tr.stop());
|
|
35295
|
-
} catch {}
|
|
35296
|
-
}
|
|
35297
35284
|
/** The push stream key: copy it, or change it (typed, or a new random one). */
|
|
35298
35285
|
function KeyRow({ value, t, onChanged }) {
|
|
35299
35286
|
const [edit, setEdit] = (0, react.useState)(null);
|
|
@@ -35488,7 +35475,9 @@ function LiveView(props) {
|
|
|
35488
35475
|
setSrcTabRaw(v);
|
|
35489
35476
|
setError("");
|
|
35490
35477
|
};
|
|
35491
|
-
const devices = useDevices(srcTab === "camera");
|
|
35478
|
+
const devices = useDevices(srcTab === "camera" || job?.source === "device");
|
|
35479
|
+
const [accessIssue, setAccessIssue] = (0, react.useState)(null);
|
|
35480
|
+
const [asking, setAsking] = (0, react.useState)(false);
|
|
35492
35481
|
const [camVideo, setCamVideoRaw] = (0, react.useState)(() => store.get("device.video", ""));
|
|
35493
35482
|
const [camAudio, setCamAudioRaw] = (0, react.useState)(() => store.get("device.audio", ""));
|
|
35494
35483
|
const setCamVideo = (v) => {
|
|
@@ -35966,10 +35955,13 @@ function LiveView(props) {
|
|
|
35966
35955
|
const statusDetail = job?.status === "running" && !job.message && !job.vod ? CLOCK(Math.max(0, (tick - job.startedAt) / 1e3)) : "";
|
|
35967
35956
|
const missing = deps.filter((d) => !d.ok && (d.name === "ffmpeg" || d.name === "yt-dlp" || (engine || "local") === "local" && d.name.startsWith("whisper")));
|
|
35968
35957
|
const needsModel = !hasTranslator && shownLines.some((l) => l !== "src");
|
|
35969
|
-
const hasMain = !!job && !(job.status === "error" && !job.files.
|
|
35958
|
+
const hasMain = !!job && !(job.status === "error" && !job.files.some((f) => /\.(mp4|m4a|srt|vtt)$/.test(f)));
|
|
35959
|
+
(0, react.useEffect)(() => {
|
|
35960
|
+
if (job && !hasMain) setSrcTabRaw(job.source === "device" ? "camera" : job.source === "push" ? "push" : job.source === "file" ? "file" : "link");
|
|
35961
|
+
}, [job?.id, hasMain]);
|
|
35970
35962
|
const cams = (devices ?? []).filter((d) => d.kind !== "mic"), mics = (devices ?? []).filter((d) => d.kind === "mic");
|
|
35971
|
-
const camV = camVideo === "none" || cams.some((d) => d.id === camVideo) ? camVideo : cams.find((d) => d.kind === "camera")?.id ?? "none";
|
|
35972
|
-
const camA = mics.some((d) => d.id === camAudio) ? camAudio : mics[0]?.id ?? "";
|
|
35963
|
+
const camV = camVideo === "none" || cams.some((d) => d.id === camVideo) ? camVideo : cams.find((d) => d.preferred)?.id ?? cams.find((d) => d.kind === "camera")?.id ?? "none";
|
|
35964
|
+
const camA = mics.some((d) => d.id === camAudio) ? camAudio : mics.find((d) => d.preferred)?.id ?? mics[0]?.id ?? "";
|
|
35973
35965
|
const videoOptions = [...cams.map((d) => ({
|
|
35974
35966
|
value: d.id,
|
|
35975
35967
|
label: d.kind === "screen" ? t("dev.screen").replace("{n}", String(Number(/\d+/.exec(d.name)?.[0] ?? 0) + 1)) : d.name,
|
|
@@ -35982,13 +35974,48 @@ function LiveView(props) {
|
|
|
35982
35974
|
const startDevice = async () => {
|
|
35983
35975
|
if (!camA) return;
|
|
35984
35976
|
const title = camV === "none" ? mics.find((d) => d.id === camA)?.name ?? "" : videoOptions.find((o) => o.value === camV)?.label ?? "";
|
|
35985
|
-
|
|
35977
|
+
const kinds = ["microphone", ...cams.find((d) => d.id === camV)?.kind === "camera" ? ["camera"] : []];
|
|
35978
|
+
setAccessIssue(null);
|
|
35979
|
+
try {
|
|
35980
|
+
let st = await (await fetch("/live/api/access")).json();
|
|
35981
|
+
for (const k of kinds) {
|
|
35982
|
+
if (st[k] !== "not-determined") continue;
|
|
35983
|
+
setAsking(true);
|
|
35984
|
+
st = {
|
|
35985
|
+
...st,
|
|
35986
|
+
[k]: (await post("access", {
|
|
35987
|
+
kind: k,
|
|
35988
|
+
action: "ask"
|
|
35989
|
+
})).state
|
|
35990
|
+
};
|
|
35991
|
+
}
|
|
35992
|
+
setAsking(false);
|
|
35993
|
+
const blocked = kinds.filter((k) => st[k] === "denied" || st[k] === "restricted");
|
|
35994
|
+
if (blocked.length) {
|
|
35995
|
+
setAccessIssue(blocked);
|
|
35996
|
+
return;
|
|
35997
|
+
}
|
|
35998
|
+
} catch {
|
|
35999
|
+
setAsking(false);
|
|
36000
|
+
}
|
|
35986
36001
|
start(JSON.stringify({
|
|
35987
36002
|
video: camV === "none" ? void 0 : camV,
|
|
35988
36003
|
audio: camA,
|
|
35989
36004
|
title
|
|
35990
36005
|
}), "device");
|
|
35991
36006
|
};
|
|
36007
|
+
const openPrivacy = (kind) => void post("access", {
|
|
36008
|
+
kind,
|
|
36009
|
+
action: "settings"
|
|
36010
|
+
}).catch(() => {});
|
|
36011
|
+
const blockedKind = () => {
|
|
36012
|
+
try {
|
|
36013
|
+
const c = JSON.parse(job?.url ?? "{}");
|
|
36014
|
+
return cams.find((d) => d.id === c.video)?.kind === "screen" ? "screen" : "camera";
|
|
36015
|
+
} catch {
|
|
36016
|
+
return "camera";
|
|
36017
|
+
}
|
|
36018
|
+
};
|
|
35992
36019
|
const emptyBox = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35993
36020
|
className: "dshl-drop",
|
|
35994
36021
|
children: [
|
|
@@ -36292,8 +36319,8 @@ function LiveView(props) {
|
|
|
36292
36319
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
36293
36320
|
className: "dshl-btn primary",
|
|
36294
36321
|
onClick: () => void startDevice(),
|
|
36295
|
-
disabled: !camA,
|
|
36296
|
-
children: t("dev.start")
|
|
36322
|
+
disabled: !camA || asking,
|
|
36323
|
+
children: asking ? t("dev.asking") : t("dev.start")
|
|
36297
36324
|
})] }),
|
|
36298
36325
|
srcTab === "push" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
36299
36326
|
className: "dshl-field static",
|
|
@@ -36390,13 +36417,37 @@ function LiveView(props) {
|
|
|
36390
36417
|
className: "dshl-note",
|
|
36391
36418
|
children: t("note.noModel")
|
|
36392
36419
|
}),
|
|
36393
|
-
|
|
36420
|
+
accessIssue && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
36394
36421
|
className: "dshl-note warn",
|
|
36395
|
-
children: [
|
|
36396
|
-
|
|
36397
|
-
|
|
36398
|
-
|
|
36399
|
-
|
|
36422
|
+
children: [
|
|
36423
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("dev.denied").replace("{d}", accessIssue.map((k) => t(`dev.k.${k}`)).join(t("dev.and"))) }),
|
|
36424
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
36425
|
+
className: "dshl-link",
|
|
36426
|
+
onClick: () => openPrivacy(accessIssue[0]),
|
|
36427
|
+
children: t("dev.openSettings")
|
|
36428
|
+
}),
|
|
36429
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
36430
|
+
className: "dshl-link",
|
|
36431
|
+
onClick: () => void startDevice(),
|
|
36432
|
+
children: t("st.retry")
|
|
36433
|
+
})
|
|
36434
|
+
]
|
|
36435
|
+
}),
|
|
36436
|
+
!accessIssue && (error || job?.status === "error") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
36437
|
+
className: "dshl-note warn",
|
|
36438
|
+
children: [
|
|
36439
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error || (job?.message === "device-blocked" ? t("dev.blocked") : job?.message === "restored" ? t("st.failedEarlier") : job?.message) }),
|
|
36440
|
+
job?.message === "device-blocked" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
36441
|
+
className: "dshl-link",
|
|
36442
|
+
onClick: () => openPrivacy(blockedKind()),
|
|
36443
|
+
children: t("dev.openSettings")
|
|
36444
|
+
}),
|
|
36445
|
+
job && !active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
36446
|
+
className: "dshl-link",
|
|
36447
|
+
onClick: () => start(job.url, job.source),
|
|
36448
|
+
children: t("st.retry")
|
|
36449
|
+
})
|
|
36450
|
+
]
|
|
36400
36451
|
}),
|
|
36401
36452
|
!hasMain ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
36402
36453
|
className: "dshl-empty",
|
|
@@ -37305,7 +37356,15 @@ const en = {
|
|
|
37305
37356
|
"src.push.bar": "Waiting for OBS: server and key below",
|
|
37306
37357
|
"src.push.wait": "Getting the address ready…",
|
|
37307
37358
|
"src.push.busy": "Close the current video to receive a stream",
|
|
37308
|
-
"dev.blocked": "No picture or sound from the device
|
|
37359
|
+
"dev.blocked": "No picture or sound from the device: DeepSeek Harness needs the system’s permission. Open System Settings, switch DeepSeek Harness on, then retry.",
|
|
37360
|
+
"dev.denied": "DeepSeek Harness needs permission to use the {d}. Open System Settings, switch it on in the list, then retry.",
|
|
37361
|
+
"dev.and": " and ",
|
|
37362
|
+
"st.failedEarlier": "This one did not finish last time.",
|
|
37363
|
+
"dev.k.camera": "camera",
|
|
37364
|
+
"dev.k.microphone": "microphone",
|
|
37365
|
+
"dev.k.screen": "screen",
|
|
37366
|
+
"dev.openSettings": "Open System Settings",
|
|
37367
|
+
"dev.asking": "Allow it in the system dialog…",
|
|
37309
37368
|
"dev.video": "Picture",
|
|
37310
37369
|
"dev.audio": "Microphone",
|
|
37311
37370
|
"dev.cameras": "Cameras",
|
|
@@ -37736,7 +37795,15 @@ const zh = {
|
|
|
37736
37795
|
"src.push.bar": "等 OBS 推流过来,服务器和密钥在下面",
|
|
37737
37796
|
"src.push.wait": "正在准备推流地址…",
|
|
37738
37797
|
"src.push.busy": "关掉当前视频后可以接收推流",
|
|
37739
|
-
"dev.blocked": "
|
|
37798
|
+
"dev.blocked": "设备没有传来画面和声音:DeepSeek Harness 需要系统授权。点「打开系统设置」,在列表里打开 DeepSeek Harness,再点重试。",
|
|
37799
|
+
"dev.denied": "DeepSeek Harness 需要{d}权限。点「打开系统设置」,在列表里打开它,再点重试。",
|
|
37800
|
+
"dev.and": "和",
|
|
37801
|
+
"st.failedEarlier": "上次这个没有成功。",
|
|
37802
|
+
"dev.k.camera": "摄像头",
|
|
37803
|
+
"dev.k.microphone": "麦克风",
|
|
37804
|
+
"dev.k.screen": "屏幕录制",
|
|
37805
|
+
"dev.openSettings": "打开系统设置",
|
|
37806
|
+
"dev.asking": "请在系统弹窗里点允许…",
|
|
37740
37807
|
"dev.video": "画面",
|
|
37741
37808
|
"dev.audio": "麦克风",
|
|
37742
37809
|
"dev.cameras": "摄像头",
|
package/lib/index.mjs
CHANGED
|
@@ -1829,7 +1829,7 @@ async function listDevices() {
|
|
|
1829
1829
|
kind: /^Capture screen/.test(name) ? "screen" : "camera"
|
|
1830
1830
|
});
|
|
1831
1831
|
}
|
|
1832
|
-
return out.filter((d) => !(d.kind === "mic" && /speakers|audio device|blackhole|soundflower|loopback/i.test(d.name)));
|
|
1832
|
+
return preferLocal(out.filter((d) => !(d.kind === "mic" && /speakers|audio device|streaming|virtual|blackhole|soundflower|loopback/i.test(d.name))), await defaultInput());
|
|
1833
1833
|
}
|
|
1834
1834
|
if (process.platform === "win32") {
|
|
1835
1835
|
const text = await ffmpegList([
|
|
@@ -1873,6 +1873,39 @@ async function listDevices() {
|
|
|
1873
1873
|
kind: "mic"
|
|
1874
1874
|
}];
|
|
1875
1875
|
}
|
|
1876
|
+
/** The system's default input device (macOS), by name. */
|
|
1877
|
+
function defaultInput() {
|
|
1878
|
+
return new Promise((resolve) => {
|
|
1879
|
+
execFile("system_profiler", ["SPAudioDataType", "-json"], { timeout: 8e3 }, (err, out) => {
|
|
1880
|
+
if (err) return resolve(void 0);
|
|
1881
|
+
try {
|
|
1882
|
+
resolve((JSON.parse(String(out)).SPAudioDataType?.[0]?._items ?? []).find((i) => i.coreaudio_default_audio_input_device === "spaudio_yes")?._name);
|
|
1883
|
+
} catch {
|
|
1884
|
+
resolve(void 0);
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Mark (and list first) this computer's own camera and the default microphone, ahead of a phone's camera
|
|
1891
|
+
* through Continuity ("“Name”的相机", "Name's Camera") or a desk-view camera.
|
|
1892
|
+
*/
|
|
1893
|
+
function preferLocal(list, defaultMic) {
|
|
1894
|
+
const phone = (d) => /^[“"].*[”"]/.test(d.name) || /['’]s (camera|microphone)/i.test(d.name);
|
|
1895
|
+
const deskView = (d) => /桌上视角|desk view/i.test(d.name);
|
|
1896
|
+
const cams = list.filter((d) => d.kind === "camera");
|
|
1897
|
+
const cam = cams.find((d) => !phone(d) && !deskView(d) && /macbook|imac|facetime|built-in|内建|display/i.test(d.name)) ?? cams.find((d) => !phone(d) && !deskView(d)) ?? cams[0];
|
|
1898
|
+
const mics = list.filter((d) => d.kind === "mic");
|
|
1899
|
+
const mic = mics.find((d) => d.name === defaultMic) ?? mics.find((d) => !phone(d)) ?? mics[0];
|
|
1900
|
+
for (const d of [cam, mic]) if (d) d.preferred = true;
|
|
1901
|
+
const rank = (d) => d.preferred ? 0 : phone(d) || deskView(d) ? 2 : 1;
|
|
1902
|
+
const group = {
|
|
1903
|
+
camera: 0,
|
|
1904
|
+
screen: 1,
|
|
1905
|
+
mic: 2
|
|
1906
|
+
};
|
|
1907
|
+
return [...list].sort((a, b) => group[a.kind] - group[b.kind] || rank(a) - rank(b));
|
|
1908
|
+
}
|
|
1876
1909
|
/** ffmpeg input for the chosen devices: arguments plus which streams carry the picture and the sound. */
|
|
1877
1910
|
function deviceInput(c, q) {
|
|
1878
1911
|
if (!c.audio) throw new Error("choose a microphone");
|
|
@@ -2086,7 +2119,8 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
2086
2119
|
heights: this.heights,
|
|
2087
2120
|
startedAt: this.startedAt,
|
|
2088
2121
|
subs: this.subs,
|
|
2089
|
-
status: this.status
|
|
2122
|
+
status: this.status,
|
|
2123
|
+
...this.status === "error" ? { message: this.message } : {}
|
|
2090
2124
|
}, null, 2));
|
|
2091
2125
|
} catch {}
|
|
2092
2126
|
}
|
|
@@ -2100,7 +2134,7 @@ var LiveJob = class LiveJob extends EventEmitter {
|
|
|
2100
2134
|
job.duration = j.duration;
|
|
2101
2135
|
job.captureDone = true;
|
|
2102
2136
|
job.status = j.status === "error" ? "error" : "stopped";
|
|
2103
|
-
job.message = "restored";
|
|
2137
|
+
job.message = j.status === "error" && j.message ? j.message : "restored";
|
|
2104
2138
|
const seen = /* @__PURE__ */ new Map();
|
|
2105
2139
|
if (existsSync(join(dir, "cues.jsonl"))) for (const line of readFileSync(join(dir, "cues.jsonl"), "utf8").split("\n")) {
|
|
2106
2140
|
if (!line.trim()) continue;
|
|
@@ -5778,6 +5812,82 @@ function metered(llm, meter) {
|
|
|
5778
5812
|
}
|
|
5779
5813
|
};
|
|
5780
5814
|
}
|
|
5815
|
+
// src/access.ts
|
|
5816
|
+
const LOAD = "$.NSBundle.bundleWithPath(\"/System/Library/Frameworks/AVFoundation.framework\").load; const D = $.NSClassFromString(\"AVCaptureDevice\");";
|
|
5817
|
+
const MEDIA = {
|
|
5818
|
+
camera: "vide",
|
|
5819
|
+
microphone: "soun"
|
|
5820
|
+
};
|
|
5821
|
+
const STATES = [
|
|
5822
|
+
"not-determined",
|
|
5823
|
+
"restricted",
|
|
5824
|
+
"denied",
|
|
5825
|
+
"granted"
|
|
5826
|
+
];
|
|
5827
|
+
function jxa(script, timeout) {
|
|
5828
|
+
return new Promise((resolve) => {
|
|
5829
|
+
execFile("osascript", [
|
|
5830
|
+
"-l",
|
|
5831
|
+
"JavaScript",
|
|
5832
|
+
"-e",
|
|
5833
|
+
script
|
|
5834
|
+
], { timeout }, (err, out) => resolve(err ? "" : String(out).trim()));
|
|
5835
|
+
});
|
|
5836
|
+
}
|
|
5837
|
+
/** This app's camera and microphone permission (macOS); 'unknown' elsewhere. */
|
|
5838
|
+
async function accessStatus() {
|
|
5839
|
+
if (process.platform !== "darwin") return {
|
|
5840
|
+
camera: "unknown",
|
|
5841
|
+
microphone: "unknown"
|
|
5842
|
+
};
|
|
5843
|
+
const out = await jxa(`${LOAD} JSON.stringify({ camera: D.authorizationStatusForMediaType("vide"), microphone: D.authorizationStatusForMediaType("soun") })`, 1e4);
|
|
5844
|
+
try {
|
|
5845
|
+
const j = JSON.parse(out);
|
|
5846
|
+
return {
|
|
5847
|
+
camera: STATES[Number(j.camera)] ?? "unknown",
|
|
5848
|
+
microphone: STATES[Number(j.microphone)] ?? "unknown"
|
|
5849
|
+
};
|
|
5850
|
+
} catch {
|
|
5851
|
+
return {
|
|
5852
|
+
camera: "unknown",
|
|
5853
|
+
microphone: "unknown"
|
|
5854
|
+
};
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
/** Ask once (the system dialog); answers the state afterwards. Already decided: just the state. */
|
|
5858
|
+
async function askAccess(kind) {
|
|
5859
|
+
if (process.platform !== "darwin") return "unknown";
|
|
5860
|
+
const before = (await accessStatus())[kind];
|
|
5861
|
+
if (before !== "not-determined") return before;
|
|
5862
|
+
await jxa(`${LOAD} let done = null; D.requestAccessForMediaTypeCompletionHandler("${MEDIA[kind]}", ok => { done = ok });
|
|
5863
|
+
const end = Date.now() + 120000;
|
|
5864
|
+
while (done === null && Date.now() < end) $.NSRunLoop.currentRunLoop.runUntilDate($.NSDate.dateWithTimeIntervalSinceNow(0.2));
|
|
5865
|
+
String(done)`, 13e4);
|
|
5866
|
+
return (await accessStatus())[kind];
|
|
5867
|
+
}
|
|
5868
|
+
/** Open the system's privacy page for this kind of device, where the app can be switched on. */
|
|
5869
|
+
function openPrivacySettings(kind) {
|
|
5870
|
+
if (process.platform === "darwin") {
|
|
5871
|
+
spawn("open", [`x-apple.systempreferences:com.apple.preference.security?${kind === "camera" ? "Privacy_Camera" : kind === "microphone" ? "Privacy_Microphone" : "Privacy_ScreenCapture"}`], {
|
|
5872
|
+
stdio: "ignore",
|
|
5873
|
+
detached: true
|
|
5874
|
+
}).unref();
|
|
5875
|
+
return true;
|
|
5876
|
+
}
|
|
5877
|
+
if (process.platform === "win32") {
|
|
5878
|
+
spawn("cmd", [
|
|
5879
|
+
"/c",
|
|
5880
|
+
"start",
|
|
5881
|
+
"",
|
|
5882
|
+
`ms-settings:${kind === "camera" ? "privacy-webcam" : kind === "microphone" ? "privacy-microphone" : "privacy"}`
|
|
5883
|
+
], {
|
|
5884
|
+
stdio: "ignore",
|
|
5885
|
+
detached: true
|
|
5886
|
+
}).unref();
|
|
5887
|
+
return true;
|
|
5888
|
+
}
|
|
5889
|
+
return false;
|
|
5890
|
+
}
|
|
5781
5891
|
// src/updates.ts
|
|
5782
5892
|
const DAY = 864e5;
|
|
5783
5893
|
/** pnpm holds back releases younger than a day: offer only versions it will install. */
|
|
@@ -6492,6 +6602,14 @@ function apply(ctx, config) {
|
|
|
6492
6602
|
}
|
|
6493
6603
|
if (route === "version") return send(res, 200, req.method === "POST" ? await updates.check() : updates.state());
|
|
6494
6604
|
if (route === "update" && req.method === "POST") return send(res, 200, await updates.install());
|
|
6605
|
+
if (route === "access") {
|
|
6606
|
+
if (req.method !== "POST") return send(res, 200, await accessStatus());
|
|
6607
|
+
const b = await readJson(req);
|
|
6608
|
+
const kind = String(b.kind);
|
|
6609
|
+
if (b.action === "settings") return send(res, 200, { opened: openPrivacySettings(kind) });
|
|
6610
|
+
if (kind !== "camera" && kind !== "microphone") return send(res, 400, { error: "camera or microphone" });
|
|
6611
|
+
return send(res, 200, { state: await askAccess(kind) });
|
|
6612
|
+
}
|
|
6495
6613
|
if (route === "devices") {
|
|
6496
6614
|
const devices = await listDevices();
|
|
6497
6615
|
log(`devices: ${devices.filter((d) => d.kind !== "mic").length} picture, ${devices.filter((d) => d.kind === "mic").length} microphone`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vidi",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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",
|