@vestra-player/sdk 1.5.9 → 1.5.11
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 +26 -0
- package/dist/commands.d.ts +7 -4
- package/dist/i18n-tools.js +59 -3
- package/dist/i18n.d.ts +92 -0
- package/dist/i18n.js +299 -0
- package/dist/player-runtime.d.ts +2 -1
- package/dist/player-runtime.js +2 -1
- package/dist/plugin/bindings.js +1 -0
- package/dist/plugin/commands.js +24 -4
- package/dist/plugin/index.js +9 -1
- package/dist/plugin/language.d.ts +6 -2
- package/dist/plugin/language.js +74 -0
- package/dist/plugin/osd.d.ts +10 -2
- package/dist/plugin/osd.js +35 -16
- package/dist/plugin/subtitles.js +9 -1
- package/dist/plugin/testing.d.ts +54 -5
- package/dist/plugin/testing.js +199 -49
- package/dist/plugin/timeline.js +3 -1
- package/dist/plugin/types.d.ts +338 -43
- package/dist/plugin/ui.js +26 -10
- package/dist/plugin/values.d.ts +55 -0
- package/dist/plugin/values.js +117 -0
- package/dist/source-resolvers.d.ts +0 -1
- package/dist/source-resolvers.js +9 -1
- package/dist/subtitles.d.ts +3 -1
- package/dist/wire.d.ts +17 -9
- package/dist/wire.js +2 -1
- package/llms-full.txt +295 -47
- package/llms.txt +41 -41
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** The player's limits on one property value, for {@link TestHost}. */
|
|
2
|
+
import type { Json } from "../wire.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The player's limits on one property value (a `PropValue` as written
|
|
5
|
+
* by `View.set` or `View.bind`, or an initial property of a
|
|
6
|
+
* window, panel or settings page). The player's tests check that these
|
|
7
|
+
* agree with the player; {@link TestHost} enforces them the same way.
|
|
8
|
+
*
|
|
9
|
+
* A value over a limit is refused whole with code `limit` (the property
|
|
10
|
+
* keeps what it showed) and reported to `Scope.onError`: a write by
|
|
11
|
+
* `View.set` or `View.bind` as described there. As an initial property it
|
|
12
|
+
* makes `Window.show` reject; a panel or settings page opens without it and
|
|
13
|
+
* the failure goes to the plugin's `onError`.
|
|
14
|
+
*
|
|
15
|
+
* They bound the work one write costs the player's interface thread; they
|
|
16
|
+
* are not meant as list sizes to aim for. A list that can grow without
|
|
17
|
+
* bound (a playlist, a history) should send the rows near the view and say
|
|
18
|
+
* how many there are, not the whole list.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { UI_VALUE_LIMITS } from "@vestra-player/sdk/testing";
|
|
23
|
+
*
|
|
24
|
+
* // 8 fields per row: 9 values (the row and its fields).
|
|
25
|
+
* const window = Math.floor(UI_VALUE_LIMITS.values / 9) - 1;
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @since 1.5.10
|
|
29
|
+
*/
|
|
30
|
+
export declare const UI_VALUE_LIMITS: {
|
|
31
|
+
/** Nesting depth; the value itself is depth 0 (8). */
|
|
32
|
+
readonly depth: 8;
|
|
33
|
+
/**
|
|
34
|
+
* Values in all, counting every array, object, string, number, boolean
|
|
35
|
+
* and null, the value itself included; a Markdown document is one
|
|
36
|
+
* (16384).
|
|
37
|
+
*/
|
|
38
|
+
readonly values: 16384;
|
|
39
|
+
/** Rows of one array (4096). */
|
|
40
|
+
readonly arrayRows: 4096;
|
|
41
|
+
/** UTF-8 bytes of one string or object field name (64 KiB). */
|
|
42
|
+
readonly stringBytes: number;
|
|
43
|
+
/**
|
|
44
|
+
* UTF-8 bytes of all strings and field names together, the text of the
|
|
45
|
+
* value's Markdown documents included (1 MiB).
|
|
46
|
+
*/
|
|
47
|
+
readonly textBytes: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Why the player would refuse `value` (in wire form) as a property value,
|
|
51
|
+
* or `null`. The same checks, order and messages as the player's
|
|
52
|
+
* conversion (`host/src/ui_values.rs`). An image reference as the whole
|
|
53
|
+
* value is not counted (the player loads it instead).
|
|
54
|
+
*/
|
|
55
|
+
export declare function valueLimitProblem(value: Json): string | null;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The player's limits on one property value (a `PropValue` as written
|
|
3
|
+
* by `View.set` or `View.bind`, or an initial property of a
|
|
4
|
+
* window, panel or settings page). The player's tests check that these
|
|
5
|
+
* agree with the player; {@link TestHost} enforces them the same way.
|
|
6
|
+
*
|
|
7
|
+
* A value over a limit is refused whole with code `limit` (the property
|
|
8
|
+
* keeps what it showed) and reported to `Scope.onError`: a write by
|
|
9
|
+
* `View.set` or `View.bind` as described there. As an initial property it
|
|
10
|
+
* makes `Window.show` reject; a panel or settings page opens without it and
|
|
11
|
+
* the failure goes to the plugin's `onError`.
|
|
12
|
+
*
|
|
13
|
+
* They bound the work one write costs the player's interface thread; they
|
|
14
|
+
* are not meant as list sizes to aim for. A list that can grow without
|
|
15
|
+
* bound (a playlist, a history) should send the rows near the view and say
|
|
16
|
+
* how many there are, not the whole list.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { UI_VALUE_LIMITS } from "@vestra-player/sdk/testing";
|
|
21
|
+
*
|
|
22
|
+
* // 8 fields per row: 9 values (the row and its fields).
|
|
23
|
+
* const window = Math.floor(UI_VALUE_LIMITS.values / 9) - 1;
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @since 1.5.10
|
|
27
|
+
*/
|
|
28
|
+
export const UI_VALUE_LIMITS = {
|
|
29
|
+
/** Nesting depth; the value itself is depth 0 (8). */
|
|
30
|
+
depth: 8,
|
|
31
|
+
/**
|
|
32
|
+
* Values in all, counting every array, object, string, number, boolean
|
|
33
|
+
* and null, the value itself included; a Markdown document is one
|
|
34
|
+
* (16384).
|
|
35
|
+
*/
|
|
36
|
+
values: 16384,
|
|
37
|
+
/** Rows of one array (4096). */
|
|
38
|
+
arrayRows: 4096,
|
|
39
|
+
/** UTF-8 bytes of one string or object field name (64 KiB). */
|
|
40
|
+
stringBytes: 64 * 1024,
|
|
41
|
+
/**
|
|
42
|
+
* UTF-8 bytes of all strings and field names together, the text of the
|
|
43
|
+
* value's Markdown documents included (1 MiB).
|
|
44
|
+
*/
|
|
45
|
+
textBytes: 1024 * 1024,
|
|
46
|
+
};
|
|
47
|
+
const L = UI_VALUE_LIMITS;
|
|
48
|
+
const bytes = (text) => Buffer.byteLength(text, "utf8");
|
|
49
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
50
|
+
/** Text of a Markdown document in wire form, or `null` for other JSON. */
|
|
51
|
+
function documentText(value) {
|
|
52
|
+
if (!isObject(value))
|
|
53
|
+
return null;
|
|
54
|
+
const doc = value.$vestraMarkdown;
|
|
55
|
+
return isObject(doc) && typeof doc.text === "string" ? doc.text : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Why the player would refuse `value` (in wire form) as a property value,
|
|
59
|
+
* or `null`. The same checks, order and messages as the player's
|
|
60
|
+
* conversion (`host/src/ui_values.rs`). An image reference as the whole
|
|
61
|
+
* value is not counted (the player loads it instead).
|
|
62
|
+
*/
|
|
63
|
+
export function valueLimitProblem(value) {
|
|
64
|
+
if (isObject(value) && "$vestraImage" in value)
|
|
65
|
+
return null;
|
|
66
|
+
const budget = { values: 0, text: 0 };
|
|
67
|
+
const text = (s) => {
|
|
68
|
+
const n = bytes(s);
|
|
69
|
+
if (n > L.stringBytes)
|
|
70
|
+
return `UI string or field name exceeds ${L.stringBytes} bytes`;
|
|
71
|
+
if (budget.text + n > L.textBytes)
|
|
72
|
+
return `UI value text exceeds ${L.textBytes} total bytes`;
|
|
73
|
+
budget.text += n;
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
const walk = (value, depth) => {
|
|
77
|
+
if (depth > L.depth)
|
|
78
|
+
return `UI value depth exceeds ${L.depth} (root is depth 0)`;
|
|
79
|
+
if (budget.values === L.values)
|
|
80
|
+
return `UI value exceeds ${L.values} total nodes`;
|
|
81
|
+
budget.values += 1;
|
|
82
|
+
const doc = documentText(value);
|
|
83
|
+
if (doc !== null) {
|
|
84
|
+
const n = bytes(doc);
|
|
85
|
+
if (budget.text + n > L.textBytes)
|
|
86
|
+
return `UI value text exceeds ${L.textBytes} total bytes`;
|
|
87
|
+
budget.text += n;
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === "string")
|
|
91
|
+
return text(value);
|
|
92
|
+
if (Array.isArray(value)) {
|
|
93
|
+
if (value.length > L.arrayRows)
|
|
94
|
+
return `UI array exceeds ${L.arrayRows} rows`;
|
|
95
|
+
if (value.length > L.values - budget.values)
|
|
96
|
+
return "UI array cannot fit in remaining node budget";
|
|
97
|
+
for (const item of value) {
|
|
98
|
+
const problem = walk(item, depth + 1);
|
|
99
|
+
if (problem)
|
|
100
|
+
return problem;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (isObject(value)) {
|
|
105
|
+
const entries = Object.entries(value);
|
|
106
|
+
if (entries.length > L.values - budget.values)
|
|
107
|
+
return "UI object cannot fit in remaining node budget";
|
|
108
|
+
for (const [name, item] of entries) {
|
|
109
|
+
const problem = text(name) ?? walk(item, depth + 1);
|
|
110
|
+
if (problem)
|
|
111
|
+
return problem;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
};
|
|
116
|
+
return walk(value, 0);
|
|
117
|
+
}
|
package/dist/source-resolvers.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
/** Trusted extraction providers. Declarations cross JSON; handler code stays in JS. */
|
|
2
|
+
import { isTextValue, textProblem } from "./i18n.js";
|
|
1
3
|
const isProducer = (result) => result?.kind === "stream";
|
|
2
4
|
/** The words of a failure, as the user will read them. */
|
|
3
|
-
const reason = (error) =>
|
|
5
|
+
const reason = (error) => {
|
|
6
|
+
// `plugin.i18n.error(…)`: the player shows the text translated.
|
|
7
|
+
const text = error?.text;
|
|
8
|
+
if (error instanceof Error && isTextValue(text) && !textProblem(text))
|
|
9
|
+
return text;
|
|
10
|
+
return (error instanceof Error ? error.message : String(error)).slice(0, 2048);
|
|
11
|
+
};
|
|
4
12
|
export function sourceResolverClient(transport) {
|
|
5
13
|
return {
|
|
6
14
|
/** Requires source.resolve. One registered host/scheme pair, and one file
|
package/dist/subtitles.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Subtitle wire types; source pixels and renderer confirmation remain distinct. */
|
|
2
|
+
import type { TextJson } from "./i18n.ts";
|
|
2
3
|
import type { Transport } from "./wire.ts";
|
|
3
4
|
export type SubtitleTrack = {
|
|
4
5
|
kind: "embedded";
|
|
@@ -169,7 +170,8 @@ export declare function subtitleClient(transport: Transport): {
|
|
|
169
170
|
expected: SubtitleStamp;
|
|
170
171
|
inputOrigin?: SubtitleTrack;
|
|
171
172
|
format?: "srt" | "ass";
|
|
172
|
-
|
|
173
|
+
/** Source text is data; a text value is shown translated (player 0.4.3). */
|
|
174
|
+
title?: string | TextJson;
|
|
173
175
|
select?: "primary" | "secondary" | "none";
|
|
174
176
|
}) => Promise<SubtitleState>;
|
|
175
177
|
select: (target: SubtitleTrack | null, expected: SubtitleStamp, operationId: number, role?: "primary" | "secondary") => Promise<SubtitleState>;
|
package/dist/wire.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SourceAudioTrack, SourceStamp } from "./source.ts";
|
|
2
|
+
import type { ArgJson, ListJson, TextJson } from "./i18n.ts";
|
|
2
3
|
export type { ResolvedMedia, ResolvedResource, SourceResolverDeclaration, } from "./source-resolvers.ts";
|
|
3
4
|
/** Working contract. Trusted plugin runtime, not a permissions sandbox. */
|
|
4
5
|
export declare const API_VERSION: "vestra/1";
|
|
@@ -141,6 +142,8 @@ export interface BindingTable {
|
|
|
141
142
|
command: string;
|
|
142
143
|
title: string;
|
|
143
144
|
path: string[] | null;
|
|
145
|
+
/** The path's keys (player 0.4.3). */
|
|
146
|
+
key?: string[] | null;
|
|
144
147
|
scope: "global" | "panel";
|
|
145
148
|
defaults: string[];
|
|
146
149
|
current: string[];
|
|
@@ -287,10 +290,15 @@ export interface WireSystemInfo {
|
|
|
287
290
|
primary: boolean;
|
|
288
291
|
}[];
|
|
289
292
|
}
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Template arguments on the wire (`osd.show`, `osd.panel`, `dialogs.confirm`,
|
|
295
|
+
* a text's `args`): data, or text values (player 0.4.3).
|
|
296
|
+
*/
|
|
297
|
+
export type TemplateArgsJson = ArgJson[] | {
|
|
298
|
+
[name: string]: ArgJson;
|
|
293
299
|
};
|
|
300
|
+
/** A text the player shows: source text, or a text value (player 0.4.3). */
|
|
301
|
+
export type TextValueJson = string | TextJson | ListJson;
|
|
294
302
|
export interface Transport {
|
|
295
303
|
request<T>(method: string, params?: Json): Promise<T>;
|
|
296
304
|
on(event: string, listener: (data: unknown) => void): () => void;
|
|
@@ -320,8 +328,8 @@ export declare function createWire(transport: Transport): {
|
|
|
320
328
|
dialogs: {
|
|
321
329
|
open: (options: {
|
|
322
330
|
kind: "openFile" | "openFiles" | "openDirectory" | "saveFile";
|
|
323
|
-
title?:
|
|
324
|
-
fileName?:
|
|
331
|
+
title?: TextValueJson;
|
|
332
|
+
fileName?: TextValueJson;
|
|
325
333
|
}) => Promise<{
|
|
326
334
|
cancelled: boolean;
|
|
327
335
|
paths: string[];
|
|
@@ -423,7 +431,7 @@ export declare function createWire(transport: Transport): {
|
|
|
423
431
|
set: (marks: {
|
|
424
432
|
time: number;
|
|
425
433
|
kind: string;
|
|
426
|
-
title?: string;
|
|
434
|
+
title?: string | TextJson;
|
|
427
435
|
}[], sourceId?: number) => Promise<{
|
|
428
436
|
applied: boolean;
|
|
429
437
|
}>;
|
|
@@ -494,8 +502,8 @@ export declare function createWire(transport: Transport): {
|
|
|
494
502
|
};
|
|
495
503
|
/** `text` is a template in the plugin's catalog, formatted with `args` (player 0.4.3). */
|
|
496
504
|
osd: {
|
|
497
|
-
show: (text:
|
|
498
|
-
panel: (text:
|
|
505
|
+
show: (text: TextValueJson, args?: TemplateArgsJson) => Promise<void>;
|
|
506
|
+
panel: (text: TextValueJson | null, args?: TemplateArgsJson, maxColumns?: number) => Promise<void>;
|
|
499
507
|
};
|
|
500
508
|
layout: {
|
|
501
509
|
state: () => Promise<{
|
|
@@ -666,7 +674,7 @@ export declare function createWire(transport: Transport): {
|
|
|
666
674
|
expected: import("./subtitles.ts").SubtitleStamp;
|
|
667
675
|
inputOrigin?: import("./subtitles.ts").SubtitleTrack;
|
|
668
676
|
format?: "srt" | "ass";
|
|
669
|
-
title?: string;
|
|
677
|
+
title?: string | TextJson;
|
|
670
678
|
select?: "primary" | "secondary" | "none";
|
|
671
679
|
}) => Promise<import("./subtitles.ts").SubtitleState>;
|
|
672
680
|
select: (target: import("./subtitles.ts").SubtitleTrack | null, expected: import("./subtitles.ts").SubtitleStamp, operationId: number, role?: "primary" | "secondary") => Promise<import("./subtitles.ts").SubtitleState>;
|
package/dist/wire.js
CHANGED
|
@@ -199,9 +199,10 @@ export function createWire(transport) {
|
|
|
199
199
|
text,
|
|
200
200
|
...(args === undefined ? {} : { args }),
|
|
201
201
|
}),
|
|
202
|
-
panel: (text, args) => transport.request("osd.panel", {
|
|
202
|
+
panel: (text, args, maxColumns) => transport.request("osd.panel", {
|
|
203
203
|
text,
|
|
204
204
|
...(args === undefined ? {} : { args }),
|
|
205
|
+
...(maxColumns === undefined ? {} : { maxColumns }),
|
|
205
206
|
}),
|
|
206
207
|
},
|
|
207
208
|
layout: {
|