@khorsheed/dsh-reader 0.3.0 → 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/CHANGELOG.md +22 -0
- package/README.en.md +26 -121
- package/README.i18n.yaml +3 -3
- package/README.md +26 -121
- package/cordis.patch.yml +7 -0
- package/lib/client.js +29054 -6698
- package/lib/index.js +3416 -334
- package/lib/typert.host.js +2145 -259
- package/lib/typert.remote-client.d.ts +97 -3
- package/lib/typert.remote-client.js +1590 -250
- package/lib/types/captions.d.ts +99 -0
- package/lib/types/captions.js +236 -0
- package/lib/types/client/MediaPlayer.d.ts +55 -0
- package/lib/types/client/MediaPlayer.js +106 -0
- package/lib/types/client/NotesView.d.ts +57 -0
- package/lib/types/client/NotesView.js +124 -0
- package/lib/types/client/ReaderPane.d.ts +20 -3
- package/lib/types/client/ReaderPane.js +860 -164
- package/lib/types/client/SpeakerLegend.d.ts +18 -0
- package/lib/types/client/SpeakerLegend.js +26 -0
- package/lib/types/client/canvas-sync.d.ts +84 -0
- package/lib/types/client/canvas-sync.js +59 -0
- package/lib/types/client/contract.d.ts +130 -3
- package/lib/types/client/extract-article.d.ts +38 -1
- package/lib/types/client/extract-article.js +501 -28
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/icons.js +1 -1
- package/lib/types/client/index.js +81 -2
- package/lib/types/client/locales.d.ts +279 -11
- package/lib/types/client/locales.js +278 -10
- package/lib/types/client/notes.d.ts +142 -0
- package/lib/types/client/notes.js +334 -0
- package/lib/types/client/parse-rss.js +40 -0
- package/lib/types/client/preset-visibility.d.ts +93 -0
- package/lib/types/client/preset-visibility.js +115 -0
- package/lib/types/client/selectors.d.ts +5 -0
- package/lib/types/client/selectors.js +5 -0
- package/lib/types/client/session.d.ts +21 -1
- package/lib/types/client/session.js +12 -0
- package/lib/types/client/store.js +4 -1
- package/lib/types/client/translate.d.ts +26 -1
- package/lib/types/client/translate.js +29 -12
- package/lib/types/client/useLocalTranscript.d.ts +43 -0
- package/lib/types/client/useLocalTranscript.js +129 -0
- package/lib/types/client/useMedia.d.ts +68 -0
- package/lib/types/client/useMedia.js +299 -0
- package/lib/types/client/useMediaStates.d.ts +9 -0
- package/lib/types/client/useMediaStates.js +43 -0
- package/lib/types/client/useNotes.d.ts +110 -0
- package/lib/types/client/useNotes.js +423 -0
- package/lib/types/client/usePolish.d.ts +47 -0
- package/lib/types/client/usePolish.js +162 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/index.js +3 -0
- package/lib/types/local-transcribe.d.ts +171 -0
- package/lib/types/local-transcribe.js +473 -0
- package/lib/types/media-host.d.ts +91 -0
- package/lib/types/media-host.js +230 -0
- package/lib/types/media.d.ts +100 -0
- package/lib/types/media.js +207 -0
- package/lib/types/notes-types.d.ts +158 -0
- package/lib/types/notes-types.js +8 -0
- package/lib/types/notes.d.ts +213 -0
- package/lib/types/notes.js +718 -0
- package/lib/types/polish.d.ts +41 -0
- package/lib/types/polish.js +136 -0
- package/lib/types/remote.d.ts +118 -0
- package/lib/types/remote.js +151 -1
- package/lib/types/service.d.ts +71 -1
- package/lib/types/service.js +280 -59
- package/lib/types/skill.d.ts +20 -0
- package/lib/types/skill.js +78 -0
- package/lib/types/store.js +30 -8
- package/lib/types/tool.d.ts +23 -0
- package/lib/types/tool.js +40 -0
- package/lib/types/tools.d.ts +69 -0
- package/lib/types/tools.js +400 -0
- package/lib/types/types.d.ts +87 -0
- package/lib/types/types.js +66 -0
- package/lib/types/xiaoyuzhou.d.ts +43 -0
- package/lib/types/xiaoyuzhou.js +134 -0
- package/lib/types/ytdlp.d.ts +111 -0
- package/lib/types/ytdlp.js +269 -0
- package/package.json +29 -20
- package/lib/invariant.js +0 -38
- package/lib/types/invariant.d.ts +0 -29
- package/lib/types/invariant.js +0 -38
|
@@ -100,9 +100,21 @@ export function patchSession(patch) {
|
|
|
100
100
|
PAGE = next;
|
|
101
101
|
updatePlace(patch);
|
|
102
102
|
}
|
|
103
|
+
let WALL;
|
|
104
|
+
/** What makes a source's parsed entries stale: its fetch, its body, its kind. */
|
|
105
|
+
export function sourceStamp(source) {
|
|
106
|
+
return [source.kind, source.url, source.fetchedAt ?? '', source.hasBody, source.status ?? '', source.truncated === true, source.failure?.code ?? ''].join('|');
|
|
107
|
+
}
|
|
108
|
+
export function readWallCache() {
|
|
109
|
+
return WALL;
|
|
110
|
+
}
|
|
111
|
+
export function writeWallCache(cache) {
|
|
112
|
+
WALL = cache;
|
|
113
|
+
}
|
|
103
114
|
/** Drop the snapshot, here and in storage (the specs call this between cases). */
|
|
104
115
|
export function forgetSession() {
|
|
105
116
|
PAGE = {};
|
|
117
|
+
WALL = undefined;
|
|
106
118
|
// Back to "not read yet", not to "nothing": the next read must consult storage
|
|
107
119
|
// again (clearing it first, so what it finds is the truth).
|
|
108
120
|
PLACE = null;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @module @khorsheed/dsh-reader/client/store
|
|
14
14
|
*/
|
|
15
15
|
import { defineStore } from '@deepseek-ai/dsh-client-store';
|
|
16
|
+
import { repairStoredBody } from "./extract-article.js";
|
|
16
17
|
const INITIAL = {
|
|
17
18
|
sources: [],
|
|
18
19
|
parsed: {},
|
|
@@ -169,7 +170,9 @@ export function createReaderStore() {
|
|
|
169
170
|
d.read = { ...d.read, [entryId]: true };
|
|
170
171
|
},
|
|
171
172
|
setArticle: (d, html, truncated, error) => {
|
|
172
|
-
|
|
173
|
+
// Every body reaches the screen through here, so a cached one written
|
|
174
|
+
// by an older extractor is brought up to the current SVG rules once.
|
|
175
|
+
d.articleHtml = repairStoredBody(html);
|
|
173
176
|
d.articleTruncated = truncated;
|
|
174
177
|
d.articleError = error;
|
|
175
178
|
},
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* `state.json`, and costs no tokens. That is also why this module has no
|
|
8
8
|
* Remote verb and no host counterpart — its only dependency is a global the
|
|
9
9
|
* page may or may not have, and {@link detectTranslator} is the whole
|
|
10
|
-
* degradation story (no API → no button, not a broken one).
|
|
10
|
+
* degradation story (no API → no button, not a broken one). The one exception
|
|
11
|
+
* is a translation some OTHER browser already made: the host stores those, and
|
|
12
|
+
* a page without the API may still show them ({@link applyRemembered}) — it
|
|
13
|
+
* can view a stored translation, never make a new one.
|
|
11
14
|
*
|
|
12
15
|
* The unit of translation is a SENTENCE inside one text node — never a node,
|
|
13
16
|
* never a paragraph. Two reasons the granularity matters:
|
|
@@ -364,6 +367,28 @@ export interface RunTranslationResult {
|
|
|
364
367
|
}
|
|
365
368
|
/** Every segment of a built article, in reading order. */
|
|
366
369
|
export declare function segmentsOf(built: BuiltArticle): BuiltSegment[];
|
|
370
|
+
/** What the memory alone could paint onto a built article. */
|
|
371
|
+
export interface RememberedResult {
|
|
372
|
+
readonly done: number;
|
|
373
|
+
readonly total: number;
|
|
374
|
+
/** Units the memory served (their LRU stamps should move). */
|
|
375
|
+
readonly recalled: readonly ReaderSentenceLearn[];
|
|
376
|
+
/** Units the memory did not know, in reading order. */
|
|
377
|
+
readonly pending: readonly BuiltSegment[];
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Paint every segment the page's memory already knows, and nothing else.
|
|
381
|
+
*
|
|
382
|
+
* This is the first step of every run, and the WHOLE of one on a browser with
|
|
383
|
+
* no Translator API: there the host's stored translations (warmed into the
|
|
384
|
+
* memory first) are the only source, and a sentence they do not cover simply
|
|
385
|
+
* stays in its original.
|
|
386
|
+
*
|
|
387
|
+
* @param built - the segmented article.
|
|
388
|
+
* @param pair - the `<src>→<tgt>` label the memory is read under.
|
|
389
|
+
* @returns how many units were painted, out of how many, and the rest.
|
|
390
|
+
*/
|
|
391
|
+
export declare function applyRemembered(built: BuiltArticle, pair: string): RememberedResult;
|
|
367
392
|
/**
|
|
368
393
|
* Translate every segment of a built article, best effort.
|
|
369
394
|
*
|
|
@@ -788,24 +788,21 @@ export function segmentsOf(built) {
|
|
|
788
788
|
return built.blocks.flatMap(block => block.segments);
|
|
789
789
|
}
|
|
790
790
|
/**
|
|
791
|
-
*
|
|
791
|
+
* Paint every segment the page's memory already knows, and nothing else.
|
|
792
792
|
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
793
|
+
* This is the first step of every run, and the WHOLE of one on a browser with
|
|
794
|
+
* no Translator API: there the host's stored translations (warmed into the
|
|
795
|
+
* memory first) are the only source, and a sentence they do not cover simply
|
|
796
|
+
* stays in its original.
|
|
796
797
|
*
|
|
797
|
-
* @param
|
|
798
|
-
* @
|
|
798
|
+
* @param built - the segmented article.
|
|
799
|
+
* @param pair - the `<src>→<tgt>` label the memory is read under.
|
|
800
|
+
* @returns how many units were painted, out of how many, and the rest.
|
|
799
801
|
*/
|
|
800
|
-
export
|
|
801
|
-
const { built, session, pair, cancelled, onProgress } = options;
|
|
802
|
+
export function applyRemembered(built, pair) {
|
|
802
803
|
const segments = segmentsOf(built);
|
|
803
|
-
const total = segments.length;
|
|
804
804
|
let done = 0;
|
|
805
|
-
let failed = 0;
|
|
806
|
-
const learned = [];
|
|
807
805
|
const recalled = [];
|
|
808
|
-
const report = () => { onProgress?.(done, total); };
|
|
809
806
|
const pending = [];
|
|
810
807
|
for (const segment of segments) {
|
|
811
808
|
const hit = remembered(pair, segment.original);
|
|
@@ -818,6 +815,26 @@ export async function runTranslation(options) {
|
|
|
818
815
|
pending.push(segment);
|
|
819
816
|
}
|
|
820
817
|
}
|
|
818
|
+
return { done, total: segments.length, recalled, pending };
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Translate every segment of a built article, best effort.
|
|
822
|
+
*
|
|
823
|
+
* Remembered units paint immediately; the rest go out in batches of whole
|
|
824
|
+
* sentences. A batch whose separator comes back altered is re-sent one unit at
|
|
825
|
+
* a time — the fallback gives up granularity for that batch, never correctness.
|
|
826
|
+
*
|
|
827
|
+
* @param options - the built article, a created session, and the cancel flag.
|
|
828
|
+
* @returns how many units were translated, how many failed, and the total.
|
|
829
|
+
*/
|
|
830
|
+
export async function runTranslation(options) {
|
|
831
|
+
const { built, session, pair, cancelled, onProgress } = options;
|
|
832
|
+
const memory = applyRemembered(built, pair);
|
|
833
|
+
const { total, recalled, pending } = memory;
|
|
834
|
+
let done = memory.done;
|
|
835
|
+
let failed = 0;
|
|
836
|
+
const learned = [];
|
|
837
|
+
const report = () => { onProgress?.(done, total); };
|
|
821
838
|
report();
|
|
822
839
|
let batch = [];
|
|
823
840
|
let chars = 0;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { LocalTranscriptStatus, ReaderMedia } from '../types.ts';
|
|
2
|
+
import type { ReaderPaneProps } from './contract.ts';
|
|
3
|
+
/** What the hook reads. */
|
|
4
|
+
export interface UseLocalTranscriptInput {
|
|
5
|
+
readonly entry: {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly link?: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly media?: ReaderMedia;
|
|
10
|
+
} | undefined;
|
|
11
|
+
/** True once the pane knows the entry has no captions and no stored transcript. */
|
|
12
|
+
readonly wanted: boolean;
|
|
13
|
+
/** The 字幕 switch is off: no transcription. */
|
|
14
|
+
readonly hidden: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* A stored local transcript is on screen: the host may still be working on
|
|
17
|
+
* it (the speakers pass runs on after the transcript is stored), so the
|
|
18
|
+
* pane asks once and follows it.
|
|
19
|
+
*/
|
|
20
|
+
readonly rejoin?: boolean;
|
|
21
|
+
readonly remote: Pick<ReaderPaneProps, 'startLocalTranscript' | 'localTranscript' | 'cancelLocalTranscript'>;
|
|
22
|
+
/** The transcript so far (`partial`) or in full. */
|
|
23
|
+
readonly onHtml: (entryId: string, html: string, partial: boolean) => void;
|
|
24
|
+
}
|
|
25
|
+
/** What the pane renders. */
|
|
26
|
+
export interface LocalTranscriptFace {
|
|
27
|
+
readonly status: LocalTranscriptStatus | undefined;
|
|
28
|
+
/** Seconds left, once the rate is measurable. */
|
|
29
|
+
readonly etaSeconds: number | undefined;
|
|
30
|
+
/** Confirm the first model download and start. */
|
|
31
|
+
readonly confirm: () => void;
|
|
32
|
+
/** Start again after a failure or a stop. */
|
|
33
|
+
readonly retry: () => void;
|
|
34
|
+
/** Stop the running job (取消, or the 字幕 switch). */
|
|
35
|
+
readonly stop: () => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The open entry's local transcription.
|
|
39
|
+
*
|
|
40
|
+
* @param input - the entry and what the pane knows.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useLocalTranscript(input: UseLocalTranscriptInput): LocalTranscriptFace;
|
|
43
|
+
//# sourceMappingURL=useLocalTranscript.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local transcription of the open video / audio entry when it has no captions
|
|
3
|
+
* (2026-10-20 decision: automatic on opening the detail, never on refresh).
|
|
4
|
+
*
|
|
5
|
+
* The host runs the job (`startLocalTranscript`); this hook starts it once per
|
|
6
|
+
* entry, polls its status while it runs — the stretches transcribed so far
|
|
7
|
+
* replace the body as they arrive, so they can be read and noted at once —
|
|
8
|
+
* and hands the finished transcript over. Leaving the entry withdraws a job
|
|
9
|
+
* that is still queued (a running one finishes and is stored); the 字幕 switch
|
|
10
|
+
* and 取消 stop it outright. The first use asks once to download the speech
|
|
11
|
+
* models.
|
|
12
|
+
*
|
|
13
|
+
* @module @khorsheed/dsh-reader/client/useLocalTranscript
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
16
|
+
/** How often a running job is polled (ms). */
|
|
17
|
+
const POLL_MS = 1500;
|
|
18
|
+
/** States in which the job is alive on the host. */
|
|
19
|
+
const ACTIVE = new Set(['fetching', 'queued', 'transcribing', 'speakers']);
|
|
20
|
+
/**
|
|
21
|
+
* The open entry's local transcription.
|
|
22
|
+
*
|
|
23
|
+
* @param input - the entry and what the pane knows.
|
|
24
|
+
*/
|
|
25
|
+
export function useLocalTranscript(input) {
|
|
26
|
+
const { entry, wanted, hidden, rejoin, remote, onHtml } = input;
|
|
27
|
+
const [statusOf, setStatusOf] = useState(undefined);
|
|
28
|
+
const status = statusOf !== undefined && statusOf.entryId === entry?.id ? statusOf : undefined;
|
|
29
|
+
const started = useRef(null);
|
|
30
|
+
const lastHtml = useRef(undefined);
|
|
31
|
+
const rate = useRef(undefined);
|
|
32
|
+
const [eta, setEta] = useState(undefined);
|
|
33
|
+
const remoteRef = useRef(remote);
|
|
34
|
+
remoteRef.current = remote;
|
|
35
|
+
const onHtmlRef = useRef(onHtml);
|
|
36
|
+
onHtmlRef.current = onHtml;
|
|
37
|
+
const accept = useCallback((next) => {
|
|
38
|
+
setStatusOf(next);
|
|
39
|
+
if (next.html !== undefined && next.html !== lastHtml.current) {
|
|
40
|
+
lastHtml.current = next.html;
|
|
41
|
+
onHtmlRef.current(next.entryId, next.html, next.state !== 'done');
|
|
42
|
+
}
|
|
43
|
+
if (next.state === 'transcribing' && next.progress !== undefined) {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
if (rate.current === undefined)
|
|
46
|
+
rate.current = { at: now, progress: next.progress };
|
|
47
|
+
else {
|
|
48
|
+
const done = next.progress - rate.current.progress;
|
|
49
|
+
setEta(done > 0.02 ? ((now - rate.current.at) / 1000) * ((1 - next.progress) / done) : undefined);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, []);
|
|
53
|
+
const begin = useCallback(async (confirmModels) => {
|
|
54
|
+
if (entry?.link === undefined || entry.media === undefined)
|
|
55
|
+
return;
|
|
56
|
+
started.current = entry.id;
|
|
57
|
+
lastHtml.current = undefined;
|
|
58
|
+
rate.current = undefined;
|
|
59
|
+
setEta(undefined);
|
|
60
|
+
const answer = await remoteRef.current.startLocalTranscript({
|
|
61
|
+
entryId: entry.id, url: entry.link, media: entry.media, title: entry.title, ...(confirmModels ? { confirmModels: true } : {}),
|
|
62
|
+
});
|
|
63
|
+
if (answer.ok)
|
|
64
|
+
accept(answer.value);
|
|
65
|
+
else
|
|
66
|
+
setStatusOf({ entryId: entry.id, state: 'failed', error: answer.error.message });
|
|
67
|
+
}, [entry, accept]);
|
|
68
|
+
// Start once per entry, when the pane says there is nothing else to read.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (entry === undefined || !wanted || hidden || started.current === entry.id)
|
|
71
|
+
return;
|
|
72
|
+
void begin(false);
|
|
73
|
+
}, [entry, wanted, hidden, begin]);
|
|
74
|
+
// Reopened onto a stored local transcript: join the host's job if it is still running.
|
|
75
|
+
const rejoined = useRef(null);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (entry === undefined || rejoin !== true || rejoined.current === entry.id)
|
|
78
|
+
return;
|
|
79
|
+
rejoined.current = entry.id;
|
|
80
|
+
const id = entry.id;
|
|
81
|
+
void remoteRef.current.localTranscript({ entryId: id }).then(answer => {
|
|
82
|
+
if (!answer.ok || !ACTIVE.has(answer.value.state))
|
|
83
|
+
return;
|
|
84
|
+
started.current = id;
|
|
85
|
+
lastHtml.current = answer.value.html;
|
|
86
|
+
setStatusOf(answer.value);
|
|
87
|
+
});
|
|
88
|
+
}, [entry, rejoin]);
|
|
89
|
+
// Poll while the host works.
|
|
90
|
+
const active = status !== undefined && ACTIVE.has(status.state);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!active || entry === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const id = entry.id;
|
|
95
|
+
const timer = setInterval(() => {
|
|
96
|
+
void remoteRef.current.localTranscript({ entryId: id }).then(answer => { if (answer.ok)
|
|
97
|
+
accept(answer.value); });
|
|
98
|
+
}, POLL_MS);
|
|
99
|
+
return () => { clearInterval(timer); };
|
|
100
|
+
}, [active, entry, accept]);
|
|
101
|
+
// Leaving the entry withdraws a queued job (a running one finishes on the host).
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const id = entry?.id;
|
|
104
|
+
return () => {
|
|
105
|
+
if (id !== undefined && started.current === id) {
|
|
106
|
+
started.current = null;
|
|
107
|
+
void remoteRef.current.cancelLocalTranscript({ entryId: id, force: false });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}, [entry?.id]);
|
|
111
|
+
const stop = useCallback(() => {
|
|
112
|
+
if (entry === undefined)
|
|
113
|
+
return;
|
|
114
|
+
void remoteRef.current.cancelLocalTranscript({ entryId: entry.id, force: true });
|
|
115
|
+
setStatusOf({ entryId: entry.id, state: 'idle' });
|
|
116
|
+
}, [entry]);
|
|
117
|
+
// The 字幕 switch turned off mid-job stops it.
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (hidden && active)
|
|
120
|
+
stop();
|
|
121
|
+
}, [hidden, active, stop]);
|
|
122
|
+
return {
|
|
123
|
+
status,
|
|
124
|
+
etaSeconds: active ? eta : undefined,
|
|
125
|
+
confirm: () => { void begin(true); },
|
|
126
|
+
retry: () => { void begin(false); },
|
|
127
|
+
stop,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The detail view's media half, as one hook: the transcript's state, the
|
|
3
|
+
* 「字幕」 switch, the remembered position, the current paragraph and
|
|
4
|
+
* following it, seeking from a paragraph's time, and the player's size (full,
|
|
5
|
+
* or the mini bar while the reader is down in the transcript).
|
|
6
|
+
*
|
|
7
|
+
* Following: the playing paragraph scrolls into view until the reader
|
|
8
|
+
* scrolls by hand; then it stops — no timer pulls the page back — and
|
|
9
|
+
* 「回到正在播放」 resumes it (2026-10-20).
|
|
10
|
+
*
|
|
11
|
+
* The transcript is the article body (`<p data-t>` blocks, captions.ts). This
|
|
12
|
+
* hook only decorates it — a time chip per paragraph, drawn from a
|
|
13
|
+
* `data-label` through CSS so a selection never picks the time up — and marks
|
|
14
|
+
* the current paragraph with `data-now`. It never rewrites text nodes, which
|
|
15
|
+
* belong to `translate.ts`.
|
|
16
|
+
*
|
|
17
|
+
* @module @khorsheed/dsh-reader/client/useMedia
|
|
18
|
+
*/
|
|
19
|
+
import { type MutableRefObject } from 'react';
|
|
20
|
+
import type { ReaderMedia } from '../media.ts';
|
|
21
|
+
import type { MediaPlayerHandle } from './MediaPlayer.tsx';
|
|
22
|
+
/** Where a media entry's transcript stands. */
|
|
23
|
+
export type TranscriptState = 'loading' | 'ready' | 'none' | 'failed';
|
|
24
|
+
/** The start time a block carries (`data-t`), when it is a transcript paragraph. */
|
|
25
|
+
export declare function blockTime(element: Element | undefined): number | undefined;
|
|
26
|
+
/** What the hook reads from the pane. */
|
|
27
|
+
export interface UseMediaInput {
|
|
28
|
+
readonly entryId: string | undefined;
|
|
29
|
+
readonly media: ReaderMedia | undefined;
|
|
30
|
+
readonly articleHtml: string | null;
|
|
31
|
+
readonly articleRef: MutableRefObject<HTMLDivElement | null>;
|
|
32
|
+
readonly detailRef: MutableRefObject<HTMLDivElement | null>;
|
|
33
|
+
/** A time chip's tooltip, from its clock label. */
|
|
34
|
+
readonly chipTitle: (clock: string) => string;
|
|
35
|
+
/** A speaker's display name, for paragraphs that carry `data-spk`. */
|
|
36
|
+
readonly speakerLabel: (speaker: number) => string;
|
|
37
|
+
}
|
|
38
|
+
/** What the pane renders from. */
|
|
39
|
+
export interface MediaFace {
|
|
40
|
+
readonly media: ReaderMedia | undefined;
|
|
41
|
+
readonly transcript: TranscriptState | undefined;
|
|
42
|
+
/** Record an entry's transcript state (keyed by entry: an answer for another entry never shows). */
|
|
43
|
+
readonly setTranscript: (entryId: string, state: TranscriptState) => void;
|
|
44
|
+
readonly hidden: boolean;
|
|
45
|
+
readonly setHidden: (hidden: boolean) => void;
|
|
46
|
+
readonly startAt: number;
|
|
47
|
+
readonly playerRef: MutableRefObject<MediaPlayerHandle | null>;
|
|
48
|
+
readonly onTime: (seconds: number) => void;
|
|
49
|
+
/** Following stopped by a hand scroll, with a playing paragraph to go back to. */
|
|
50
|
+
readonly followPaused: boolean;
|
|
51
|
+
/** Scroll back to the playing paragraph and follow again. */
|
|
52
|
+
readonly resumeFollow: () => void;
|
|
53
|
+
/** The video as the mini bar. */
|
|
54
|
+
readonly mini: boolean;
|
|
55
|
+
readonly setMini: (mini: boolean) => void;
|
|
56
|
+
/** The one-time 「点时间可以跳转」 hint is still owed. */
|
|
57
|
+
readonly hintOwed: boolean;
|
|
58
|
+
readonly dismissHint: () => void;
|
|
59
|
+
/** Jump the player to a moment. */
|
|
60
|
+
readonly seek: (seconds: number) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The open entry's media state.
|
|
64
|
+
*
|
|
65
|
+
* @param input - what the pane already holds.
|
|
66
|
+
*/
|
|
67
|
+
export declare function useMedia(input: UseMediaInput): MediaFace;
|
|
68
|
+
//# sourceMappingURL=useMedia.d.ts.map
|