@vestra-player/sdk 1.5.37 → 1.5.39
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 +19 -0
- package/README.md +1 -0
- package/dist/api-requests.js +1 -0
- package/dist/host-capabilities.js +1 -0
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/appearance.js +11 -2
- package/dist/plugin/packages.js +3 -0
- package/dist/plugin/player.js +6 -1
- package/dist/plugin/resolvers.js +10 -2
- package/dist/plugin/runtime.d.ts +14 -1
- package/dist/plugin/runtime.js +107 -16
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -0
- package/dist/plugin/shaders.d.ts +9 -0
- package/dist/plugin/shaders.js +160 -0
- package/dist/plugin/subtitles.js +14 -2
- package/dist/plugin/testing.d.ts +74 -3
- package/dist/plugin/testing.js +221 -8
- package/dist/plugin/timeline.d.ts +1 -1
- package/dist/plugin/timeline.js +10 -2
- package/dist/plugin/types.d.ts +213 -22
- package/dist/plugin/ui.js +9 -2
- package/dist/plugin/window.d.ts +1 -1
- package/dist/plugin/window.js +10 -2
- package/dist/processing.d.ts +118 -4
- package/dist/processing.js +139 -0
- package/dist/wire.d.ts +24 -2
- package/dist/wire.js +19 -3
- package/llms-full.txt +318 -25
- package/llms.txt +23 -23
- package/package.json +1 -1
package/dist/processing.js
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processing chains for the player's video and audio filters. Applied means
|
|
3
|
+
* the backend accepted the chain, not that a frame with it is visible yet.
|
|
4
|
+
*/
|
|
5
|
+
import { isAbsolute } from "node:path";
|
|
6
|
+
/**
|
|
7
|
+
* The player's bounds for the video primitives of SDK 1.5.39 (player
|
|
8
|
+
* 0.5.20): `convolution` and `lut3d` {@link VideoNode}s and the shader
|
|
9
|
+
* channel (`plugin.video.shaders()`). Values beyond them fail with
|
|
10
|
+
* `invalid`; more shaders than `shaders` fail with `limit`.
|
|
11
|
+
*
|
|
12
|
+
* @example Refuse a shader list the player would not take
|
|
13
|
+
* The plugin checks a shader list against the player's limit before it
|
|
14
|
+
* sets it, and says so on the OSD instead of waiting for a `limit` failure.
|
|
15
|
+
* The limit counts every plugin's shaders, so the player still decides.
|
|
16
|
+
* ```ts
|
|
17
|
+
* import type { Plugin } from "@vestra-player/sdk";
|
|
18
|
+
* declare const plugin: Plugin;
|
|
19
|
+
* declare const files: string[];
|
|
20
|
+
* // ---cut---
|
|
21
|
+
* import { VIDEO_PROCESSING_LIMITS } from "@vestra-player/sdk/testing";
|
|
22
|
+
*
|
|
23
|
+
* if (files.length > VIDEO_PROCESSING_LIMITS.shaders)
|
|
24
|
+
* void plugin.osd.show("Too many shaders");
|
|
25
|
+
* else plugin.video.shaders().set(files);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @since 1.5.39
|
|
29
|
+
* @category Testing
|
|
30
|
+
*/
|
|
31
|
+
export const VIDEO_PROCESSING_LIMITS = Object.freeze({
|
|
32
|
+
/** Shaders across every plugin's `plugin.video.shaders()` handles (16). */
|
|
33
|
+
shaders: 16,
|
|
34
|
+
/** Bytes of one shader file (16 MiB). */
|
|
35
|
+
shaderBytes: 16 * 1024 * 1024,
|
|
36
|
+
/** Bytes of a shader handle's id (128). */
|
|
37
|
+
shaderIdBytes: 128,
|
|
38
|
+
/** Bytes of one `lut3d` `.cube` file (16 MiB). */
|
|
39
|
+
lutBytes: 16 * 1024 * 1024,
|
|
40
|
+
/** Largest `LUT_3D_SIZE` of a `lut3d` file, points per axis (65). */
|
|
41
|
+
lutSize: 65,
|
|
42
|
+
/** Bytes of a `lut3d` file path (4096). */
|
|
43
|
+
lutPathBytes: 4096,
|
|
44
|
+
/** Largest magnitude of a `convolution` matrix entry (1024). */
|
|
45
|
+
matrixValue: 1024,
|
|
46
|
+
/** Largest `convolution` `rdiv` (256). */
|
|
47
|
+
rdiv: 256,
|
|
48
|
+
/** Largest `convolution` `bias` (65535). */
|
|
49
|
+
bias: 65535,
|
|
50
|
+
});
|
|
1
51
|
const BOOL = [true, false];
|
|
2
52
|
/** Value ranges, the same as the host's `src/host/src/playback/effects.rs` (the host is authoritative). */
|
|
3
53
|
const RULES = {
|
|
@@ -25,6 +75,12 @@ const RULES = {
|
|
|
25
75
|
},
|
|
26
76
|
reframe: { mode: ["crop", "pad"], aspect: [0.25, 4], x: [-1, 1], y: [-1, 1] },
|
|
27
77
|
frameBlend: { fps: [24, 120] },
|
|
78
|
+
// Checked by `convolutionProblem` / `lutProblem` below.
|
|
79
|
+
convolution: { planes: [] },
|
|
80
|
+
lut3d: {
|
|
81
|
+
file: [],
|
|
82
|
+
interpolation: ["nearest", "trilinear", "tetrahedral", "pyramid", "prism"],
|
|
83
|
+
},
|
|
28
84
|
volume: { gainDb: [-60, 0] },
|
|
29
85
|
equalizer: { frequency: [20, 20000], q: [1], gainDb: [-6, 6] },
|
|
30
86
|
treble: { frequency: [3000], slope: [0.5], gainDb: [0, 6] },
|
|
@@ -43,7 +99,77 @@ const VIDEO = new Set([
|
|
|
43
99
|
"deinterlace",
|
|
44
100
|
"reframe",
|
|
45
101
|
"frameBlend",
|
|
102
|
+
"convolution",
|
|
103
|
+
"lut3d",
|
|
46
104
|
]);
|
|
105
|
+
/** `convolution` nodes: the player's rules (`effects.rs ConvolutionPlane`). */
|
|
106
|
+
function convolutionProblem(planes) {
|
|
107
|
+
if (!Array.isArray(planes) || planes.length < 1 || planes.length > 4)
|
|
108
|
+
return "convolution planes must have 1 to 4 entries";
|
|
109
|
+
if (planes.every((plane) => plane === null))
|
|
110
|
+
return "convolution needs at least one plane with a matrix";
|
|
111
|
+
const limits = VIDEO_PROCESSING_LIMITS;
|
|
112
|
+
for (const [index, plane] of planes.entries()) {
|
|
113
|
+
if (plane === null)
|
|
114
|
+
continue;
|
|
115
|
+
if (typeof plane !== "object" || Array.isArray(plane))
|
|
116
|
+
return `convolution plane ${index} must be an object or null`;
|
|
117
|
+
const fields = Object.keys(plane);
|
|
118
|
+
const unknown = fields.find((field) => !["mode", "matrix", "rdiv", "bias"].includes(field));
|
|
119
|
+
if (unknown)
|
|
120
|
+
return `convolution plane ${index}: unknown field ${unknown}`;
|
|
121
|
+
const { mode, matrix, rdiv, bias } = plane;
|
|
122
|
+
if (!["square", "row", "column"].includes(mode))
|
|
123
|
+
return `convolution plane ${index}: mode must be square, row or column`;
|
|
124
|
+
if (!Array.isArray(matrix))
|
|
125
|
+
return `convolution plane ${index}: matrix must be an array of integers`;
|
|
126
|
+
const sized = mode === "square"
|
|
127
|
+
? [9, 25, 49].includes(matrix.length)
|
|
128
|
+
: matrix.length % 2 === 1 && matrix.length <= 49;
|
|
129
|
+
if (!sized)
|
|
130
|
+
return `convolution plane ${index}: a square matrix has 9, 25 or 49 entries, a row or column an odd number up to 49 (got ${matrix.length})`;
|
|
131
|
+
if (matrix.some((value) => !Number.isInteger(value) || Math.abs(value) > limits.matrixValue))
|
|
132
|
+
return `convolution plane ${index}: matrix entries must be integers in -${limits.matrixValue}..${limits.matrixValue}`;
|
|
133
|
+
const outside = (value, max) => typeof value !== "number" ||
|
|
134
|
+
!Number.isFinite(value) ||
|
|
135
|
+
value < 0 ||
|
|
136
|
+
value > max;
|
|
137
|
+
if (outside(rdiv, limits.rdiv))
|
|
138
|
+
return `convolution plane ${index} rdiv must be finite in 0..=${limits.rdiv}`;
|
|
139
|
+
if (outside(bias, limits.bias))
|
|
140
|
+
return `convolution plane ${index} bias must be finite in 0..=${limits.bias}`;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether `text` has a control character as Rust's `char::is_control`
|
|
146
|
+
* sees one (C0, DEL and C1: U+0000–U+001F, U+007F–U+009F).
|
|
147
|
+
*/
|
|
148
|
+
export function hasControl(text) {
|
|
149
|
+
return [...text].some((c) => {
|
|
150
|
+
const code = c.codePointAt(0);
|
|
151
|
+
return code < 0x20 || (code >= 0x7f && code <= 0x9f);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The form of a `lut3d` path (the player's `effects.rs lut_path`); where
|
|
156
|
+
* the file is and what it holds the player checks when the request arrives
|
|
157
|
+
* (the test host too, `TestHost.fileRoots`).
|
|
158
|
+
*/
|
|
159
|
+
export function lutPathProblem(file) {
|
|
160
|
+
const max = VIDEO_PROCESSING_LIMITS.lutPathBytes;
|
|
161
|
+
if (typeof file !== "string" ||
|
|
162
|
+
Buffer.byteLength(file) > max ||
|
|
163
|
+
hasControl(file))
|
|
164
|
+
return `lut3d file must be a path of at most ${max} bytes without control characters`;
|
|
165
|
+
if (/[[\]]/.test(file))
|
|
166
|
+
return "lut3d file path must not contain [ or ]";
|
|
167
|
+
if (!isAbsolute(file))
|
|
168
|
+
return "lut3d file must be an absolute path";
|
|
169
|
+
if (!/\.cube$/i.test(file))
|
|
170
|
+
return "lut3d file must be a .cube file";
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
47
173
|
/**
|
|
48
174
|
* Why `nodes` would be refused (`null` when valid): unknown kinds or fields,
|
|
49
175
|
* values out of range, more than 64 nodes. Used by the test host.
|
|
@@ -59,7 +185,20 @@ export function nodeProblem(stage, nodes) {
|
|
|
59
185
|
for (const field of fields)
|
|
60
186
|
if (!(field in rule))
|
|
61
187
|
return `${node.kind}: unknown field ${field}`;
|
|
188
|
+
if (node.kind === "convolution") {
|
|
189
|
+
const problem = convolutionProblem(node.planes);
|
|
190
|
+
if (problem)
|
|
191
|
+
return problem;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (node.kind === "lut3d") {
|
|
195
|
+
const problem = lutPathProblem(node.file);
|
|
196
|
+
if (problem)
|
|
197
|
+
return problem;
|
|
198
|
+
}
|
|
62
199
|
for (const [field, allowed] of Object.entries(rule)) {
|
|
200
|
+
if (allowed.length === 0)
|
|
201
|
+
continue;
|
|
63
202
|
const value = node[field];
|
|
64
203
|
const ok = allowed.length === 2 &&
|
|
65
204
|
typeof allowed[0] === "number" &&
|
package/dist/wire.d.ts
CHANGED
|
@@ -141,6 +141,8 @@ export interface PackageInfo {
|
|
|
141
141
|
description: string | null;
|
|
142
142
|
tags: string[];
|
|
143
143
|
};
|
|
144
|
+
/** Whether it has a process now (player 0.5.18, `state: "running"`). */
|
|
145
|
+
process?: "running" | "idle";
|
|
144
146
|
}
|
|
145
147
|
/** Where an installed package came from (`packages.install` `source`). */
|
|
146
148
|
export type WireSource = {
|
|
@@ -365,6 +367,8 @@ export interface Transport {
|
|
|
365
367
|
*/
|
|
366
368
|
locale?: string;
|
|
367
369
|
}
|
|
370
|
+
/** The events a plugin subscribes to (`events.subscribe`). */
|
|
371
|
+
type HostEvent = "source.state" | "playback.backend" | "window.state" | "packages.changed" | "bindings.changed" | "timeline.changed" | "appearance.changed" | "resolvers.fileTypes";
|
|
368
372
|
export declare function createWire(transport: Transport): {
|
|
369
373
|
processing: {
|
|
370
374
|
state: () => Promise<import("./processing.ts").ProcessingState>;
|
|
@@ -477,6 +481,13 @@ export declare function createWire(transport: Transport): {
|
|
|
477
481
|
state: "applied" | "waiting";
|
|
478
482
|
}>;
|
|
479
483
|
renderRelease: () => Promise<void>;
|
|
484
|
+
/**
|
|
485
|
+
* One shader channel handle's list (`plugin.video.shaders(id)`, player
|
|
486
|
+
* 0.5.20); `[]` removes it.
|
|
487
|
+
*/
|
|
488
|
+
shadersSet: (id: string, paths: string[]) => Promise<{
|
|
489
|
+
state: "applied" | "waiting";
|
|
490
|
+
}>;
|
|
480
491
|
/** The GPU's video upscaler (`plugin.video.upscaling`, player 0.5.16). */
|
|
481
492
|
upscalingState: () => Promise<WireUpscalingState>;
|
|
482
493
|
upscalingSet: (enabled: boolean) => Promise<WireUpscalingState>;
|
|
@@ -599,8 +610,19 @@ export declare function createWire(transport: Transport): {
|
|
|
599
610
|
}) => Promise<void>;
|
|
600
611
|
};
|
|
601
612
|
events: {
|
|
602
|
-
/**
|
|
603
|
-
|
|
613
|
+
/**
|
|
614
|
+
* One `events.subscribe` per name however many listeners. `mirror`:
|
|
615
|
+
* only the SDK keeps a copy of the state (player 0.5.18; older
|
|
616
|
+
* players ignore it); `listen` changes it.
|
|
617
|
+
*/
|
|
618
|
+
subscribe(event: HostEvent, listener: (data: unknown) => void, mirror?: boolean): Promise<() => Promise<void>>;
|
|
619
|
+
/**
|
|
620
|
+
* Tell the player whether a subscribed event has a real listener
|
|
621
|
+
* (`active`) or only the SDK's copy of the state: `events.subscribe`
|
|
622
|
+
* again, which replaces the mark (player 0.5.18; older players keep
|
|
623
|
+
* one subscription per name and ignore the mark).
|
|
624
|
+
*/
|
|
625
|
+
listen: (event: HostEvent, active: boolean) => Promise<void>;
|
|
604
626
|
};
|
|
605
627
|
ui: {
|
|
606
628
|
/** Player 0.2.0: the remote images a Markdown document shows. */
|
package/dist/wire.js
CHANGED
|
@@ -138,6 +138,11 @@ export function createWire(transport) {
|
|
|
138
138
|
/** The GPU renderer's options (`plugin.video.render()`). */
|
|
139
139
|
renderSet: (values) => transport.request("video.render.set", { values }),
|
|
140
140
|
renderRelease: () => transport.request("video.render.release"),
|
|
141
|
+
/**
|
|
142
|
+
* One shader channel handle's list (`plugin.video.shaders(id)`, player
|
|
143
|
+
* 0.5.20); `[]` removes it.
|
|
144
|
+
*/
|
|
145
|
+
shadersSet: (id, paths) => transport.request("video.shaders.set", { id, paths }),
|
|
141
146
|
/** The GPU's video upscaler (`plugin.video.upscaling`, player 0.5.16). */
|
|
142
147
|
upscalingState: () => transport.request("video.upscaling.state"),
|
|
143
148
|
upscalingSet: (enabled) => transport.request("video.upscaling.set", {
|
|
@@ -255,14 +260,18 @@ export function createWire(transport) {
|
|
|
255
260
|
set: (options) => transport.request("layout.set", options),
|
|
256
261
|
},
|
|
257
262
|
events: {
|
|
258
|
-
/**
|
|
259
|
-
|
|
263
|
+
/**
|
|
264
|
+
* One `events.subscribe` per name however many listeners. `mirror`:
|
|
265
|
+
* only the SDK keeps a copy of the state (player 0.5.18; older
|
|
266
|
+
* players ignore it); `listen` changes it.
|
|
267
|
+
*/
|
|
268
|
+
async subscribe(event, listener, mirror = false) {
|
|
260
269
|
const off = transport.on(event, listener);
|
|
261
270
|
let subscription = subscriptions.get(event);
|
|
262
271
|
if (!subscription) {
|
|
263
272
|
subscription = {
|
|
264
273
|
count: 0,
|
|
265
|
-
ready: transport.request("events.subscribe", { event }),
|
|
274
|
+
ready: transport.request("events.subscribe", mirror ? { event, mirror: true } : { event }),
|
|
266
275
|
};
|
|
267
276
|
subscriptions.set(event, subscription);
|
|
268
277
|
}
|
|
@@ -287,6 +296,13 @@ export function createWire(transport) {
|
|
|
287
296
|
}
|
|
288
297
|
};
|
|
289
298
|
},
|
|
299
|
+
/**
|
|
300
|
+
* Tell the player whether a subscribed event has a real listener
|
|
301
|
+
* (`active`) or only the SDK's copy of the state: `events.subscribe`
|
|
302
|
+
* again, which replaces the mark (player 0.5.18; older players keep
|
|
303
|
+
* one subscription per name and ignore the mark).
|
|
304
|
+
*/
|
|
305
|
+
listen: (event, active) => transport.request("events.subscribe", active ? { event } : { event, mirror: true }),
|
|
290
306
|
},
|
|
291
307
|
ui: {
|
|
292
308
|
/** Player 0.2.0: the remote images a Markdown document shows. */
|