@vestra-player/sdk 1.5.39 → 1.5.41
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 +15 -0
- package/bin/vestra-plugin.mjs +5 -1
- package/dist/commands.d.ts +12 -2
- package/dist/commands.js +4 -2
- package/dist/plugin/commands.js +41 -8
- package/dist/plugin/runtime.d.ts +31 -1
- package/dist/plugin/runtime.js +154 -12
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -0
- package/dist/plugin/testing.d.ts +57 -2
- package/dist/plugin/testing.js +121 -1
- package/dist/plugin/types.d.ts +244 -11
- package/dist/plugin/ui.js +6 -4
- package/dist/plugin/when.d.ts +46 -0
- package/dist/plugin/when.js +106 -0
- package/dist/wire.d.ts +4 -4
- package/dist/wire.js +13 -3
- package/llms-full.txt +241 -16
- package/llms.txt +19 -19
- package/package.json +1 -1
- package/slint/vestra/bar-buttons.slint +23 -6
package/dist/wire.d.ts
CHANGED
|
@@ -376,11 +376,11 @@ export declare function createWire(transport: Transport): {
|
|
|
376
376
|
operation: (token: import("./processing.ts").ProcessingToken) => Promise<import("./processing.ts").ProcessingOperation>;
|
|
377
377
|
};
|
|
378
378
|
commands: {
|
|
379
|
-
register: (id: string, revision: number, declaration: import("./commands.ts").CommandDeclaration, state: import("./commands.ts").CommandState) => Promise<{
|
|
379
|
+
register: (id: string, revision: number, declaration: import("./commands.ts").CommandDeclaration, state: import("./commands.ts").CommandState, when?: import("./commands.ts").CommandWhen) => Promise<{
|
|
380
380
|
revision: number;
|
|
381
381
|
}>;
|
|
382
382
|
unregister: (id: string) => Promise<void>;
|
|
383
|
-
update: (id: string, revision: number, state: import("./commands.ts").CommandState) => Promise<{
|
|
383
|
+
update: (id: string, revision: number, state: import("./commands.ts").CommandState, when?: import("./commands.ts").CommandWhen) => Promise<{
|
|
384
384
|
revision: number;
|
|
385
385
|
}>;
|
|
386
386
|
};
|
|
@@ -615,14 +615,14 @@ export declare function createWire(transport: Transport): {
|
|
|
615
615
|
* only the SDK keeps a copy of the state (player 0.5.18; older
|
|
616
616
|
* players ignore it); `listen` changes it.
|
|
617
617
|
*/
|
|
618
|
-
subscribe(event: HostEvent, listener: (data: unknown) => void, mirror?: boolean): Promise<() => Promise<void>>;
|
|
618
|
+
subscribe(event: HostEvent, listener: (data: unknown) => void, mirror?: boolean, why?: string[]): Promise<() => Promise<void>>;
|
|
619
619
|
/**
|
|
620
620
|
* Tell the player whether a subscribed event has a real listener
|
|
621
621
|
* (`active`) or only the SDK's copy of the state: `events.subscribe`
|
|
622
622
|
* again, which replaces the mark (player 0.5.18; older players keep
|
|
623
623
|
* one subscription per name and ignore the mark).
|
|
624
624
|
*/
|
|
625
|
-
listen: (event: HostEvent, active: boolean) => Promise<void>;
|
|
625
|
+
listen: (event: HostEvent, active: boolean, why?: string[]) => Promise<void>;
|
|
626
626
|
};
|
|
627
627
|
ui: {
|
|
628
628
|
/** Player 0.2.0: the remote images a Markdown document shows. */
|
package/dist/wire.js
CHANGED
|
@@ -265,13 +265,19 @@ export function createWire(transport) {
|
|
|
265
265
|
* only the SDK keeps a copy of the state (player 0.5.18; older
|
|
266
266
|
* players ignore it); `listen` changes it.
|
|
267
267
|
*/
|
|
268
|
-
async subscribe(event, listener, mirror = false
|
|
268
|
+
async subscribe(event, listener, mirror = false,
|
|
269
|
+
/** What listens (player 0.5.21 logs it; older players ignore it). */
|
|
270
|
+
why) {
|
|
269
271
|
const off = transport.on(event, listener);
|
|
270
272
|
let subscription = subscriptions.get(event);
|
|
271
273
|
if (!subscription) {
|
|
272
274
|
subscription = {
|
|
273
275
|
count: 0,
|
|
274
|
-
ready: transport.request("events.subscribe", mirror
|
|
276
|
+
ready: transport.request("events.subscribe", mirror
|
|
277
|
+
? { event, mirror: true }
|
|
278
|
+
: why?.length
|
|
279
|
+
? { event, why }
|
|
280
|
+
: { event }),
|
|
275
281
|
};
|
|
276
282
|
subscriptions.set(event, subscription);
|
|
277
283
|
}
|
|
@@ -302,7 +308,11 @@ export function createWire(transport) {
|
|
|
302
308
|
* again, which replaces the mark (player 0.5.18; older players keep
|
|
303
309
|
* one subscription per name and ignore the mark).
|
|
304
310
|
*/
|
|
305
|
-
listen: (event, active) => transport.request("events.subscribe", active
|
|
311
|
+
listen: (event, active, why) => transport.request("events.subscribe", active
|
|
312
|
+
? why?.length
|
|
313
|
+
? { event, why }
|
|
314
|
+
: { event }
|
|
315
|
+
: { event, mirror: true }),
|
|
306
316
|
},
|
|
307
317
|
ui: {
|
|
308
318
|
/** Player 0.2.0: the remote images a Markdown document shows. */
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.41: full reference
|
|
2
2
|
|
|
3
3
|
> Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
|
|
4
4
|
|
|
@@ -2710,6 +2710,7 @@ The table lists the API whose documentation names a player version, newest last:
|
|
|
2710
2710
|
| 0.5.13 | SystemInfo.renderingReason, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState, Rendering |
|
|
2711
2711
|
| 0.5.16 | UpscalingReasonCode, UpscalingReason, UpscalingState, VideoUpscaling |
|
|
2712
2712
|
| 0.5.20 | VideoShaders |
|
|
2713
|
+
| 0.5.21 | Scope.when, When, Bindings.onChange |
|
|
2713
2714
|
|
|
2714
2715
|
The Slint library carries no version tags: `Theme.danger` needs player 0.3.0; `@vestra/icons.slint` and the palette `vestra-light` need 0.4.0 (Theme).
|
|
2715
2716
|
|
|
@@ -3329,7 +3330,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
|
|
|
3329
3330
|
| `order` | Integer 0–999, left to right ascending; equal orders by plugin id, then declaration order. The built-in plugins use 100 (Vestra Player AI) and 200 (Plugin Store). |
|
|
3330
3331
|
| `path` | Required: a button without a menu path is `invalid`. |
|
|
3331
3332
|
|
|
3332
|
-
- **Shown**: the icon; the tooltip is the command's shown title without a trailing "…" and its first active key ("Plugin Store (F9)"), plus the reason on a second line while the command is disabled or busy. Disabled or busy: drawn disabled, a click runs nothing. `checked: true`: the icon in the accent colour. Accessibility: a button named by the title, with its enabled and checked state and a default action.
|
|
3333
|
+
- **Shown**: the icon; the tooltip is the command's shown title without a trailing "…" and its first active key ("Plugin Store (F9)"), plus the reason on a second line while the command is disabled or busy. The icon is drawn in the accent colour (player 0.5.22; the text colour before), darker while pressed. Disabled or busy: drawn disabled, a click runs nothing. `checked: true`: a small dot in the icon's colour under the icon (before player 0.5.22: the icon in the accent colour, the others in the text colour). Accessibility: a button named by the title, with its enabled and checked state and a default action.
|
|
3333
3334
|
- **Click**: runs the command exactly as a click on its menu item does.
|
|
3334
3335
|
- **Order and room**: left to right by `order`, the player's **Toggle Sidebar** rightmost; in a narrow window the strip loses cells from its outer (left) end inward: the lowest order goes first, the sidebar toggle last. `order` is position only: low is further left and goes first.
|
|
3335
3336
|
- **Limits** (Limits): one button per plugin (its first declared), eight in the player (the highest orders; the lowest go first, ties: the smaller plugin id, then the earlier declaration). A button past them is left out and logged with `limit`; the command and its menu entry stay.
|
|
@@ -3897,6 +3898,10 @@ rest of the plugin keeps running.
|
|
|
3897
3898
|
interface Scope extends Disposable {
|
|
3898
3899
|
/** Menu commands and their default bindings (`commands`). */
|
|
3899
3900
|
readonly commands: Commands;
|
|
3901
|
+
/**
|
|
3902
|
+
* Conditions on the player's own state for a command's `enabled` and `checked` (SDK 1.5.40, player 0.5.21): the player evaluates them, so the menu follows playback even while the plugin is stopped. See When.
|
|
3903
|
+
*/
|
|
3904
|
+
readonly when: When;
|
|
3900
3905
|
/** Tool windows from `.slint` files (`ui.window`). */
|
|
3901
3906
|
readonly windows: Windows;
|
|
3902
3907
|
/** Docked panels: bottom bar, sidebar and backdrop (`ui.mount`). */
|
|
@@ -4238,6 +4243,191 @@ plugin.commands.add({
|
|
|
4238
4243
|
});
|
|
4239
4244
|
```
|
|
4240
4245
|
|
|
4246
|
+
### Condition
|
|
4247
|
+
|
|
4248
|
+
A condition on the player's own state, from Scope.when, for a
|
|
4249
|
+
command's `enabled` or `checked`. The player evaluates it, again on
|
|
4250
|
+
every change of that state, whether or not the plugin runs.
|
|
4251
|
+
|
|
4252
|
+
Combine conditions with `and`, `or` and `not`; each returns a new
|
|
4253
|
+
condition and leaves the original as it was. A condition is data: it
|
|
4254
|
+
reads nothing in the plugin and keeps no listener, so a plugin whose
|
|
4255
|
+
menu state is made of conditions may be stopped while idle (lazy
|
|
4256
|
+
activation) and its menu stays current. A function (see Bindable)
|
|
4257
|
+
keeps working; one that reads `plugin.player.state` or another service's
|
|
4258
|
+
state keeps the plugin running so the SDK can evaluate it.
|
|
4259
|
+
|
|
4260
|
+
- @capability commands
|
|
4261
|
+
|
|
4262
|
+
```ts
|
|
4263
|
+
interface Condition {
|
|
4264
|
+
/**
|
|
4265
|
+
* True when this condition and every one of `others` hold.
|
|
4266
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4267
|
+
* @example Combine two conditions with and
|
|
4268
|
+
* The item is enabled only while a file plays in fullscreen. `and`
|
|
4269
|
+
* returns a new condition; `when.playing` itself is unchanged.
|
|
4270
|
+
* ```ts
|
|
4271
|
+
* const { when } = plugin;
|
|
4272
|
+
* const playingFullscreen = when.playing.and(when.fullscreen);
|
|
4273
|
+
* ```
|
|
4274
|
+
*/
|
|
4275
|
+
and(...others: Condition[]): Condition;
|
|
4276
|
+
/**
|
|
4277
|
+
* True when this condition or one of `others` holds.
|
|
4278
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4279
|
+
* @example Combine two conditions with or
|
|
4280
|
+
* The item is enabled while a file plays or is paused. `or` returns a
|
|
4281
|
+
* new condition that holds when either of the two holds.
|
|
4282
|
+
* ```ts
|
|
4283
|
+
* const { when } = plugin;
|
|
4284
|
+
* const active = when.playing.or(when.paused);
|
|
4285
|
+
* ```
|
|
4286
|
+
*/
|
|
4287
|
+
or(...others: Condition[]): Condition;
|
|
4288
|
+
/**
|
|
4289
|
+
* True when this condition does not hold.
|
|
4290
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4291
|
+
* @example Negate a condition with not
|
|
4292
|
+
* An "Enter Fullscreen" item is enabled only outside fullscreen. `not`
|
|
4293
|
+
* returns the opposite condition; the player evaluates it on every
|
|
4294
|
+
* window change.
|
|
4295
|
+
* ```ts
|
|
4296
|
+
* const { when } = plugin;
|
|
4297
|
+
* const windowed = when.fullscreen.not();
|
|
4298
|
+
* ```
|
|
4299
|
+
*/
|
|
4300
|
+
not(): Condition;
|
|
4301
|
+
}
|
|
4302
|
+
```
|
|
4303
|
+
|
|
4304
|
+
Example: Enable an item while a frame can be saved
|
|
4305
|
+
|
|
4306
|
+
"Save Snapshot" is greyed out with its reason unless a media file is
|
|
4307
|
+
open at a frame the player can capture. The condition is sent with the
|
|
4308
|
+
command and evaluated by the player, so the item follows playback even
|
|
4309
|
+
while the plugin is stopped; with an `enabled` condition the player
|
|
4310
|
+
shows the `reason` only while the item is disabled.
|
|
4311
|
+
```ts
|
|
4312
|
+
const { when } = plugin;
|
|
4313
|
+
plugin.commands.add({
|
|
4314
|
+
id: "save",
|
|
4315
|
+
path: ["@video", "Save Snapshot"],
|
|
4316
|
+
enabled: when.can("snapshot").and(when.phase("ready", "ended"), when.stream.not()),
|
|
4317
|
+
reason: "No frame to capture", // shown only while disabled
|
|
4318
|
+
run: saveSnapshot,
|
|
4319
|
+
});
|
|
4320
|
+
```
|
|
4321
|
+
|
|
4322
|
+
### When
|
|
4323
|
+
|
|
4324
|
+
`plugin.when`: the vocabulary of conditions (SDK 1.5.40, player 0.5.21).
|
|
4325
|
+
It names only state the player owns: the source's phase, kind, pause
|
|
4326
|
+
and controls (the same names as PlayerState), fullscreen, the
|
|
4327
|
+
palette and the saved interface language.
|
|
4328
|
+
|
|
4329
|
+
Give a condition as a command's `enabled` or `checked`
|
|
4330
|
+
(CommandOptions, Command.set). The player evaluates it
|
|
4331
|
+
and shows the result in menus, on keys and on bar buttons, again
|
|
4332
|
+
whenever that state changes, also while the plugin is stopped because
|
|
4333
|
+
nothing used it. With an `enabled` condition the player shows the
|
|
4334
|
+
command's `reason` only while the condition is false (or the command
|
|
4335
|
+
is busy), so a fixed reason is enough. Players before 0.5.21 have no
|
|
4336
|
+
`plugin.when` (declare `"minPlayerVersion": "0.5.21"`).
|
|
4337
|
+
|
|
4338
|
+
State the vocabulary does not name (a plugin's own settings, an open
|
|
4339
|
+
window, a track list) stays a function or a value set with
|
|
4340
|
+
Command.set.
|
|
4341
|
+
|
|
4342
|
+
- @capability commands
|
|
4343
|
+
|
|
4344
|
+
```ts
|
|
4345
|
+
interface When {
|
|
4346
|
+
/** A source is open: any phase but `empty` (loading, ready, ended, error). */
|
|
4347
|
+
readonly open: Condition;
|
|
4348
|
+
/** The source is loaded and can play (`phase` is `ready`). */
|
|
4349
|
+
readonly ready: Condition;
|
|
4350
|
+
/** The source is ready and not paused. */
|
|
4351
|
+
readonly playing: Condition;
|
|
4352
|
+
/** The source is ready and paused. */
|
|
4353
|
+
readonly paused: Condition;
|
|
4354
|
+
/** The source is a generated stream (`PlayerState.kind` is `stream`). */
|
|
4355
|
+
readonly stream: Condition;
|
|
4356
|
+
/** The main window is fullscreen. */
|
|
4357
|
+
readonly fullscreen: Condition;
|
|
4358
|
+
/**
|
|
4359
|
+
* The source's `phase` is one of `phases`.
|
|
4360
|
+
* @throws TypeError (throws) No phase, or one that is not a phase.
|
|
4361
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4362
|
+
* @example Enable an item while a file is loaded or finished
|
|
4363
|
+
* The item is enabled while the source is ready or has ended, as a
|
|
4364
|
+
* snapshot needs a frame. The phases are those of `PlayerState.phase`.
|
|
4365
|
+
* ```ts
|
|
4366
|
+
* const { when } = plugin;
|
|
4367
|
+
* const hasFrame = when.phase("ready", "ended");
|
|
4368
|
+
* ```
|
|
4369
|
+
*/
|
|
4370
|
+
phase(...phases: PlayerState["phase"][]): Condition;
|
|
4371
|
+
/**
|
|
4372
|
+
* The source allows `control`: `PlayerState.can[control]` is true.
|
|
4373
|
+
* @throws TypeError (throws) `control` is not one of `PlayerState.can`.
|
|
4374
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4375
|
+
* @example Enable a seek item only where seeking works
|
|
4376
|
+
* "Jump to Middle" is greyed out on live streams that cannot seek.
|
|
4377
|
+
* The player reads its own `can.seek` for the current source.
|
|
4378
|
+
* ```ts
|
|
4379
|
+
* const { when } = plugin;
|
|
4380
|
+
* const seekable = when.can("seek");
|
|
4381
|
+
* ```
|
|
4382
|
+
*/
|
|
4383
|
+
can(control: keyof PlayerState["can"]): Condition;
|
|
4384
|
+
/**
|
|
4385
|
+
* The palette in use is `theme` (`plugin.appearance.theme`).
|
|
4386
|
+
* @throws TypeError (throws) `theme` is not a palette of the player.
|
|
4387
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4388
|
+
* @example Check the light palette's item
|
|
4389
|
+
* The item carries a check mark while the light palette is in use,
|
|
4390
|
+
* whoever switched to it.
|
|
4391
|
+
* ```ts
|
|
4392
|
+
* const { when } = plugin;
|
|
4393
|
+
* const light = when.theme("vestra-light");
|
|
4394
|
+
* ```
|
|
4395
|
+
*/
|
|
4396
|
+
theme(theme: ThemeId): Condition;
|
|
4397
|
+
/**
|
|
4398
|
+
* The saved interface language is `language` (`plugin.appearance.language`: `"system"` or a locale).
|
|
4399
|
+
* @throws TypeError (throws) `language` is not `"system"` or a locale.
|
|
4400
|
+
* @capability None (the command it is given to needs `commands`).
|
|
4401
|
+
* @example Check the Follow System item
|
|
4402
|
+
* The item carries a check mark while the player follows the system's
|
|
4403
|
+
* language; the player reads its own saved preference.
|
|
4404
|
+
* ```ts
|
|
4405
|
+
* const { when } = plugin;
|
|
4406
|
+
* const followSystem = when.language("system");
|
|
4407
|
+
* ```
|
|
4408
|
+
*/
|
|
4409
|
+
language(language: LanguagePreference): Condition;
|
|
4410
|
+
}
|
|
4411
|
+
```
|
|
4412
|
+
|
|
4413
|
+
Example: Check a palette item and follow the palette
|
|
4414
|
+
|
|
4415
|
+
The Appearance submenu has one radio item per palette; the player
|
|
4416
|
+
checks the one in use, also after another plugin or the user changed
|
|
4417
|
+
it, and while this plugin is stopped. Nothing in the plugin listens to
|
|
4418
|
+
the palette.
|
|
4419
|
+
```ts
|
|
4420
|
+
const { when } = plugin;
|
|
4421
|
+
for (const [id, title] of [["vestra", "Dark"], ["vestra-light", "Light"]] as const)
|
|
4422
|
+
plugin.commands.add({
|
|
4423
|
+
id: `theme.${id}`,
|
|
4424
|
+
path: ["Settings", "Appearance", title],
|
|
4425
|
+
radio: true,
|
|
4426
|
+
checked: when.theme(id), // evaluated by the player, not the plugin
|
|
4427
|
+
run: () => plugin.appearance.set(id),
|
|
4428
|
+
});
|
|
4429
|
+
```
|
|
4430
|
+
|
|
4241
4431
|
### MenuId
|
|
4242
4432
|
|
|
4243
4433
|
A built-in top-level menu of the player, named in a command path as
|
|
@@ -4279,9 +4469,9 @@ Writing a bottom bar.
|
|
|
4279
4469
|
"…" removed) and its first key as the tooltip ("Plugin Store (F9)"),
|
|
4280
4470
|
`enabled`, `busy` and `reason` as the menu item has them: a disabled or
|
|
4281
4471
|
busy command's button is drawn disabled, its tooltip adds the reason, and
|
|
4282
|
-
a click runs nothing.
|
|
4283
|
-
|
|
4284
|
-
click on its menu item.
|
|
4472
|
+
a click runs nothing. Every button's icon is drawn in the accent colour;
|
|
4473
|
+
a command with `checked` shows a small dot under its icon while checked.
|
|
4474
|
+
A click runs the command exactly like a click on its menu item.
|
|
4285
4475
|
|
|
4286
4476
|
**Limits.** One button per plugin and eight in the player
|
|
4287
4477
|
(`BAR_BUTTON_LIMITS`): a button past them is left out (its command and
|
|
@@ -4439,13 +4629,15 @@ interface CommandOptions {
|
|
|
4439
4629
|
* A button for this command on the bottom bar (SDK 1.5.29, player 0.5.10): an icon and a place, `{ icon: "store", order: 200 }`. Needs a `path`: the menu entry is where the command stays reachable when the bar shows no button (a bar without `BarButtons`, a narrow window, a limit). See CommandButton for where it shows, what it shows and the limits. Fails with `invalid` for an unknown icon name, an order outside 0–999 or a button without a path.
|
|
4440
4630
|
*/
|
|
4441
4631
|
button?: CommandButton;
|
|
4442
|
-
/** Whether the command can run (default true). */
|
|
4443
|
-
enabled?: Bindable<boolean
|
|
4444
|
-
/** Check mark (or radio dot with `radio`); `null` (default) draws none. */
|
|
4445
|
-
checked?: Bindable<boolean | null
|
|
4632
|
+
/** Whether the command can run (default true): a value, a function, or a Condition the player evaluates (`plugin.when`, SDK 1.5.40). */
|
|
4633
|
+
enabled?: Bindable<boolean> | Condition;
|
|
4634
|
+
/** Check mark (or radio dot with `radio`); `null` (default) draws none. A Condition (`plugin.when`, SDK 1.5.40) draws the mark while it holds. */
|
|
4635
|
+
checked?: Bindable<boolean | null> | Condition;
|
|
4446
4636
|
/** Busy state (default false); the player does not call `run` while busy. */
|
|
4447
4637
|
busy?: Bindable<boolean>;
|
|
4448
|
-
/**
|
|
4638
|
+
/**
|
|
4639
|
+
* Why the command is disabled or busy (shown to the user): English source text, or a Text for one with data in it (SDK 1.5.11). With an `enabled` Condition the player shows it only while the condition is false or the command is busy, so a fixed text is enough.
|
|
4640
|
+
*/
|
|
4449
4641
|
reason?: Bindable<TextValue | null>;
|
|
4450
4642
|
/**
|
|
4451
4643
|
* Run the command (menu item, binding, panel menu or `host.command(id).run()` in tests). It may return a promise; errors and rejections go to Scope.onError, and bound values are re-evaluated when it returns and when its promise settles.
|
|
@@ -4497,7 +4689,10 @@ interface Command extends Disposable {
|
|
|
4497
4689
|
* ```
|
|
4498
4690
|
*/
|
|
4499
4691
|
set(state: Partial<{
|
|
4500
|
-
[K in
|
|
4692
|
+
[K in "busy" | "reason"]: Bindable<CommandState[K]>;
|
|
4693
|
+
} & {
|
|
4694
|
+
enabled: Bindable<boolean> | Condition;
|
|
4695
|
+
checked: Bindable<boolean | null> | Condition;
|
|
4501
4696
|
}>): void;
|
|
4502
4697
|
/**
|
|
4503
4698
|
* Resolves when the host has registered the command. Rejects with `conflict` (path already taken), `invalid` or `limit` (more than 160 commands in this plugin, or 1024 in the whole player). You rarely need to await it: during activation a failure fails activation, later it goes to Scope.onError.
|
|
@@ -11242,12 +11437,13 @@ interface Bindings {
|
|
|
11242
11437
|
*/
|
|
11243
11438
|
reset(ref?: BindingRef): Promise<void>;
|
|
11244
11439
|
/**
|
|
11245
|
-
*
|
|
11440
|
+
* The binding list changed (read `list()` again): a declaration or an override. At most every 200 ms; since player 0.5.21 only when the list itself changed, not when a plugin starts again with the same commands.
|
|
11246
11441
|
* @capability bindings.manage
|
|
11247
11442
|
* @example Refresh a key list when bindings change
|
|
11248
11443
|
* The plugin's key list stays current when the user changes a key in the
|
|
11249
|
-
* player or
|
|
11250
|
-
* at most every 200 ms; the plugin reads the
|
|
11444
|
+
* player or a plugin with other commands is installed. The listener
|
|
11445
|
+
* carries no data and runs at most every 200 ms; the plugin reads the
|
|
11446
|
+
* table again with `list`.
|
|
11251
11447
|
* ```ts
|
|
11252
11448
|
* plugin.bindings.onChange(async () => {
|
|
11253
11449
|
* const { commands } = await plugin.bindings.list(); // read the new table
|
|
@@ -12793,7 +12989,18 @@ interface FakeCommand {
|
|
|
12793
12989
|
*/
|
|
12794
12990
|
reason?: string | null;
|
|
12795
12991
|
};
|
|
12796
|
-
/**
|
|
12992
|
+
/**
|
|
12993
|
+
* The conditions the plugin sent for `enabled` and `checked` (`plugin.when`, wire form), `undefined` without. Like the player, the host evaluates them in FakeCommand.state against `source`, `mainWindow.fullscreen`, `appearance` and `language`.
|
|
12994
|
+
*/
|
|
12995
|
+
when?: {
|
|
12996
|
+
/** The condition of `enabled`. */
|
|
12997
|
+
enabled?: Json;
|
|
12998
|
+
/** The condition of `checked`. */
|
|
12999
|
+
checked?: Json;
|
|
13000
|
+
};
|
|
13001
|
+
/**
|
|
13002
|
+
* The command state as the player shows it: what the plugin last sent (after the update pass), with the fields given as conditions (`plugin.when`) evaluated now. With an `enabled` condition, `reason` is `null` while the command is enabled and not busy.
|
|
13003
|
+
*/
|
|
12797
13004
|
state: {
|
|
12798
13005
|
/** `false` when the plugin disabled the command. */
|
|
12799
13006
|
enabled: boolean;
|
|
@@ -12861,7 +13068,7 @@ interface FakeBarButton {
|
|
|
12861
13068
|
readonly order: number;
|
|
12862
13069
|
/** Clickable: the command is enabled and not busy. */
|
|
12863
13070
|
readonly enabled: boolean;
|
|
12864
|
-
/**
|
|
13071
|
+
/** Shown with the on dot under its icon: the command is checked. */
|
|
12865
13072
|
readonly checked: boolean;
|
|
12866
13073
|
/** The tooltip without the key: the command's shown title (trailing "…" removed), then, while it cannot run, its reason on a second line. */
|
|
12867
13074
|
readonly tooltip: string;
|
|
@@ -13013,6 +13220,10 @@ class TestHost implements Transport {
|
|
|
13013
13220
|
* The subscribed events the plugin really listens to: an `onChange` and the like, or a binding that reads the state. The SDK's own copies of the state (what keeps `plugin.player.state` current) are left out. Empty means the player may stop the plugin while nothing uses it (unless it holds something else: a window, a panel, a filter…).
|
|
13014
13221
|
*/
|
|
13015
13222
|
readonly listening: Set<string>;
|
|
13223
|
+
/**
|
|
13224
|
+
* Why the plugin listens to each event of TestHost.listening: every listener and every binding that reads its state, as the player's log says them (`a listener added at main.ts:12`, `command "Save" enabled is a function that reads plugin.player.state (main.ts:40) — use plugin.when`), in the order they came.
|
|
13225
|
+
*/
|
|
13226
|
+
readonly listeningWhy: Map<string, string[]>;
|
|
13016
13227
|
/**
|
|
13017
13228
|
* The player's source facts (phase, position, duration, paused, rate, audio tracks, loop, ...). Wire-level shape; change it with TestHost.setSource, which also broadcasts it.
|
|
13018
13229
|
*/
|
|
@@ -14493,6 +14704,20 @@ class TestHost implements Transport {
|
|
|
14493
14704
|
* ```
|
|
14494
14705
|
*/
|
|
14495
14706
|
close(): void;
|
|
14707
|
+
/**
|
|
14708
|
+
* Why the player would keep this plugin running while nothing uses it (lazy activation, player 0.5.21), one line per reason, as the player's log says it: an event it listens to that is not one the player wakes it for (with what listens and where it was added), its mounted panel, its open windows. Empty: the player may stop it while idle and start it again on its next use. Filter chains, streams, jobs and the like (which also keep a plugin running) are not listed.
|
|
14709
|
+
* @capability None.
|
|
14710
|
+
* @example Check that a plugin can be stopped while idle
|
|
14711
|
+
* The test fails if the plugin keeps running for no reason after it
|
|
14712
|
+
* starts, and names the reason (a command whose `enabled` reads
|
|
14713
|
+
* `plugin.player.state`, say: give it a `plugin.when` condition). The
|
|
14714
|
+
* player stops such a plugin while nothing uses it and starts it again
|
|
14715
|
+
* on its next use, so it costs no process meanwhile.
|
|
14716
|
+
* ```ts
|
|
14717
|
+
* assert.deepEqual(host.keepsRunning, []);
|
|
14718
|
+
* ```
|
|
14719
|
+
*/
|
|
14720
|
+
get keepsRunning(): string[];
|
|
14496
14721
|
}
|
|
14497
14722
|
```
|
|
14498
14723
|
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.41: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n>: a line of llms-full.txt (next to this file), which has every page, declaration and example.
|
|
4
4
|
|
|
5
5
|
**Rules**
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ my-plugin/
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2646–
|
|
36
|
+
Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2646–2718.
|
|
37
37
|
|
|
38
38
|
**main.ts**
|
|
39
39
|
|
|
@@ -78,9 +78,9 @@ export default async function activate(plugin: Plugin) {
|
|
|
78
78
|
- `secrets`: Secrets.get/set/delete
|
|
79
79
|
- `rendering.manage`: Rendering.setPreference/restartPlayer
|
|
80
80
|
- `video.upscaling`: VideoUpscaling.state/set
|
|
81
|
-
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details:
|
|
81
|
+
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details: L2719–2779.
|
|
82
82
|
|
|
83
|
-
**Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`, `host` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L2087–2275; TestHost:
|
|
83
|
+
**Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`, `host` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L2087–2275; TestHost: L13164.
|
|
84
84
|
|
|
85
85
|
**Command line**
|
|
86
86
|
|
|
@@ -108,7 +108,7 @@ export default async function activate(plugin: Plugin) {
|
|
|
108
108
|
- `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
|
|
109
109
|
- `reserved`: The id is `vestra` or starts with `vestra.`
|
|
110
110
|
- `versionMismatch`: The package needs a newer player
|
|
111
|
-
- Others: PluginError, llms-full.txt
|
|
111
|
+
- Others: PluginError, llms-full.txt L3798.
|
|
112
112
|
|
|
113
113
|
## Reference
|
|
114
114
|
|
|
@@ -117,30 +117,30 @@ export default async function activate(plugin: Plugin) {
|
|
|
117
117
|
- Concepts: How a plugin runs L238–418; Commands, menus and key bindings L419–530; Playback state, controls and sources L531–676; Video and audio processing L677–763; Windows, panels, settings pages, dialogs and the OSD L764–1052
|
|
118
118
|
- How-to: Control playback L1053–1181; Store a plugin's data L1182–1249; Load subtitles from a plugin L1250–1328; Keep a slider responsive while dragging L1329–1503; Remember a window's state between runs L1504–1633; Offer a GPU rendering switch L1634–1754; Translate a plugin L1755–1938; Migrate a plugin to English source text L1939–2086; Test a plugin L2087–2275; Publish a plugin L2276–2364; Migrating off package rollback (1.5.16, removed in 1.5.17) L2365–2440
|
|
119
119
|
- Guidelines: Interface guidelines L2441–2548; Menu guidelines L2549–2645
|
|
120
|
-
- Reference: Manifest L2646–
|
|
121
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html):
|
|
122
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html):
|
|
120
|
+
- Reference: Manifest L2646–2718; Capabilities L2719–2779; Limits L2780–2925; Commands, menus and bindings L2926–3409; Platforms L3410–3440; Theme L3441–3662; Command line L3663–3720
|
|
121
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3721–12837
|
|
122
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L12838–15662
|
|
123
123
|
|
|
124
124
|
## API
|
|
125
125
|
|
|
126
126
|
Where each `plugin.<module>` is declared:
|
|
127
127
|
|
|
128
|
-
- Plugin
|
|
129
|
-
- Commands
|
|
128
|
+
- Plugin L4137, Scope L3873, PluginError L3798, Disposable L3736
|
|
129
|
+
- Commands L4724; When L4322; Windows L5661; Panels L7107; Dialogs L7777; Player L8518; video: VideoFilter L9398, VideoView L9498, RenderOptions L9585, VideoShaders L9663, VideoUpscaling L9909; audio: AudioFilter L9443; Subtitles L10105; media: MediaCapture L10509; Resolvers L10739; Packages L11141; Osd L9263; Host L9105; Clipboard L11470; Power L11557; Tools L11616; Storage L11768; System L11967; Secrets L12047; window: MainWindow L7424; App L7604; Rendering L12310; Bindings L11369; Ui L5013; Timeline L7336; Drops L6354; Appearance L6435; I18n L6668; Settings L6951.
|
|
130
130
|
|
|
131
131
|
## Examples
|
|
132
132
|
|
|
133
133
|
Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
|
|
134
134
|
|
|
135
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
136
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
137
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
138
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
139
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
140
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
141
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
142
|
-
- markdown: A window that shows a Markdown document.
|
|
143
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
135
|
+
- hello: The README Quick start: one menu command and an on-screen message. L16097–16186
|
|
136
|
+
- blur: A tool window with one slider that blurs the playing video. L16187–16448
|
|
137
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L16449–16781
|
|
138
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L16782–16968
|
|
139
|
+
- panel-menu: A sidebar panel with its own right-click menu. L16969–17177
|
|
140
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L17178–17337
|
|
141
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L17338–17501
|
|
142
|
+
- markdown: A window that shows a Markdown document. L17502–17698
|
|
143
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L17699–17866
|
|
144
144
|
|
|
145
145
|
## Optional
|
|
146
146
|
|
package/package.json
CHANGED
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
// entries stay; the player logs which buttons are hidden).
|
|
13
13
|
//
|
|
14
14
|
// The cells look like the bar's own icon buttons: a 32×32 plate on hover
|
|
15
|
-
// and press, the
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// and press, the bar's tooltip (ToolTipArea). Every enabled cell draws its
|
|
16
|
+
// icon in the accent (accent-press while pressed; icon-disabled when
|
|
17
|
+
// disabled), so the strip reads as one group. A toggle that is on shows a
|
|
18
|
+
// small dot in the icon's colour centred under the icon, like a running app
|
|
19
|
+
// in the macOS Dock (player 0.5.22, SDK 1.5.41; before, icons were in the
|
|
20
|
+
// text colour and only an on toggle in the accent). They are persistent
|
|
21
|
+
// elements shown with `visible:`; nothing is created on hover or resize.
|
|
18
22
|
import { Theme } from "theme.slint";
|
|
19
23
|
import { Icon } from "icons.slint";
|
|
20
24
|
import { ToolTipArea } from "widgets.slint";
|
|
@@ -32,7 +36,7 @@ export struct BarButtonItem {
|
|
|
32
36
|
// The tooltip: the title and its key, the reason it is unavailable.
|
|
33
37
|
tip: string,
|
|
34
38
|
enabled: bool,
|
|
35
|
-
// The command has a checked state; `checked`
|
|
39
|
+
// The command has a checked state; `checked` shows the dot under the icon.
|
|
36
40
|
checkable: bool,
|
|
37
41
|
checked: bool,
|
|
38
42
|
// At half opacity: on, but not shown for want of room (the sidebar).
|
|
@@ -58,8 +62,11 @@ component Cell inherits Rectangle {
|
|
|
58
62
|
in property <bool> shown;
|
|
59
63
|
private property <bool> live: root.shown && root.item.enabled;
|
|
60
64
|
private property <brush> tint: !root.item.enabled ? Theme.icon-disabled
|
|
61
|
-
:
|
|
62
|
-
|
|
65
|
+
: touch.pressed ? Theme.accent-press : Theme.accent;
|
|
66
|
+
// The on dot: its diameter and top edge, between the icon box (y 14–26)
|
|
67
|
+
// and the hover plate's lower edge (y 36).
|
|
68
|
+
private property <length> dot-size: 4px;
|
|
69
|
+
private property <length> dot-y: 29px;
|
|
63
70
|
width: 41px;
|
|
64
71
|
height: 40px;
|
|
65
72
|
visible: root.shown;
|
|
@@ -99,6 +106,16 @@ component Cell inherits Rectangle {
|
|
|
99
106
|
image-fit: contain;
|
|
100
107
|
colorize: root.tint;
|
|
101
108
|
}
|
|
109
|
+
// On (`checked`): the dot under the icon, in the icon's colour.
|
|
110
|
+
Rectangle {
|
|
111
|
+
visible: root.item.checked;
|
|
112
|
+
x: (root.width - 1px - self.width) / 2;
|
|
113
|
+
y: root.dot-y;
|
|
114
|
+
width: root.dot-size;
|
|
115
|
+
height: root.dot-size;
|
|
116
|
+
border-radius: self.width / 2;
|
|
117
|
+
background: root.tint;
|
|
118
|
+
}
|
|
102
119
|
ToolTipArea {
|
|
103
120
|
width: 100%;
|
|
104
121
|
height: 100%;
|