@vestra-player/sdk 1.5.43 → 1.5.45
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 +21 -1
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/index.js +4 -1
- package/dist/plugin/patch.d.ts +101 -0
- package/dist/plugin/patch.js +337 -0
- package/dist/plugin/player.d.ts +11 -0
- package/dist/plugin/player.js +80 -7
- package/dist/plugin/runtime.d.ts +64 -1
- package/dist/plugin/runtime.js +227 -16
- package/dist/plugin/scope.d.ts +9 -1
- package/dist/plugin/scope.js +24 -0
- package/dist/plugin/settings.js +2 -2
- package/dist/plugin/testing.d.ts +7 -0
- package/dist/plugin/testing.js +139 -14
- package/dist/plugin/types.d.ts +24 -15
- package/dist/plugin/ui.d.ts +7 -3
- package/dist/plugin/ui.js +236 -22
- package/dist/wire.d.ts +42 -3
- package/dist/wire.js +3 -3
- package/llms-full.txt +148 -26
- package/llms.txt +23 -23
- package/package.json +1 -1
- package/slint/vestra/markdown.slint +17 -11
- package/slint/vestra/widgets.slint +770 -1
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.45: 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
|
|
|
@@ -1903,6 +1903,8 @@ host.handle("processing.replace", () => {
|
|
|
1903
1903
|
|
|
1904
1904
|
`await host.settle()` after every action that makes the plugin talk to the player; `host.settle(400)` also lets debounces and timers due by then fire. Then assert on what the host recorded: `host.window(id).props` (kebab-case names), `host.command(id).state`, `host.osd`, `host.source`.
|
|
1905
1905
|
|
|
1906
|
+
`settle` does not wait for the once-a-second full update pass (SDK 1.5.45): after `host.setSource(...)` only the bindings that read a changed field of `plugin.player.state` run again. A value your plugin changed outside a handler (a timer, a promise continuation, a child process) without `plugin.update()` shows in the next full pass, at most about a second later; call `plugin.update()` in the plugin after such a change, or wait `await host.settle(1000)` in the test.
|
|
1907
|
+
|
|
1906
1908
|
`settle` waits only for calls to the player. Work the plugin does on its own (reading or parsing a file, a child process, a network request) can finish later on a loaded machine; poll the value you assert on instead:
|
|
1907
1909
|
|
|
1908
1910
|
```ts nocheck
|
|
@@ -3242,17 +3244,21 @@ Icon { name: "close"; width: 20px; height: 20px; } // scaled
|
|
|
3242
3244
|
| `checked` | in `bool` | | Icon buttons: the "on" state, drawn in the accent colour. |
|
|
3243
3245
|
| `clicked` | callback `()` | | |
|
|
3244
3246
|
|
|
3245
|
-
**`Slider`**: 2px line slider. Bar style (default): 8px round thumb, a white core in a 2px accent ring, gradient fill. Panel style (`panel: true`): 14×12 thumb, gradient fill. The track is `Theme.line` in both.
|
|
3247
|
+
**`Slider`**: 2px line slider. Bar style (default): 8px round thumb, a white core in a 2px accent ring, gradient fill. Panel style (`panel: true`): 14×12 thumb, gradient fill. The track is `Theme.line` in both. Keyboard (focused by Tab or `focus()`; a click does not take the focus, so the bar's volume slider leaves the arrows to seeking), as std-widgets' Slider: Left/Down and Right/Up move by `step`, Page Down/Up by 10 steps, Home/End to the ends; `changed` follows every move and `released` the key's release. Screen readers can increment, decrement or set the value.
|
|
3246
3248
|
|
|
3247
3249
|
| Member | Kind | Default | Note |
|
|
3248
3250
|
| --- | --- | --- | --- |
|
|
3249
3251
|
| `value` | in-out `float` | | |
|
|
3250
3252
|
| `minimum` | in `float` | `0` | |
|
|
3251
3253
|
| `maximum` | in `float` | `1` | |
|
|
3254
|
+
| `step` | in `float` | `(root.maximum - root.minimum) / 20` | One key press: a twentieth of the range unless set. |
|
|
3252
3255
|
| `enabled` | in `bool` | `true` | |
|
|
3253
3256
|
| `panel` | in `bool` | | |
|
|
3257
|
+
| `focusable` | in `bool` | `true` | false: Tab skips the slider (the bottom bar's seek and volume sliders, so Tab never moves the arrows from seeking to the slider); `focus()` still works. |
|
|
3258
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3254
3259
|
| `changed` | callback `(float)` | | |
|
|
3255
3260
|
| `released` | callback `(float)` | | |
|
|
3261
|
+
| `set-value` | function `(v: float)` | | Clamped to the range; `changed` only when the value moves. |
|
|
3256
3262
|
|
|
3257
3263
|
**`UrlInputRow`**: Link input: a multi-line input and [Open] [Cancel]. Enter or the confirm button accepts (Shift+Enter starts a new line), Esc or Cancel cancels. No validation here: the plugin checks the address.
|
|
3258
3264
|
|
|
@@ -3268,6 +3274,93 @@ Icon { name: "close"; width: 20px; height: 20px; } // scaled
|
|
|
3268
3274
|
| `focus-input` | function `()` | | |
|
|
3269
3275
|
| `select-all` | function `()` | | |
|
|
3270
3276
|
|
|
3277
|
+
**`Check`**: Check box with a label that wraps onto more lines when the row is narrower than the label (the box stays beside the first line). A click or Space flips `checked`, then `toggled` reports the new state, as std-widgets' CheckBox: bind it two-way (`checked <=> Bridge.on`), or set it again from the plugin's data in a `changed` handler, since the click replaces a one-way binding. Plugin-decided (`self-toggle: false`, like Radio): a click or Space does not flip the box; `toggled` reports the state asked for (`!checked`) and the plugin sets `checked` when it agrees, for example after a confirmation, so a one-way binding (`checked: Bridge.on`) stays. Both modes also call `clicked`. Default style: a 13px box, accent when checked. Panel style (`panel: true`, the picture and audio workbenches' switches): a 12px box on `bg-bar` with a 6px `playing` square.
|
|
3278
|
+
|
|
3279
|
+
| Member | Kind | Default | Note |
|
|
3280
|
+
| --- | --- | --- | --- |
|
|
3281
|
+
| `text` | in `string` | | |
|
|
3282
|
+
| `checked` | in-out `bool` | | |
|
|
3283
|
+
| `enabled` | in `bool` | `true` | |
|
|
3284
|
+
| `panel` | in `bool` | | |
|
|
3285
|
+
| `self-toggle` | in `bool` | `true` | false: the plugin owns `checked` (see above). |
|
|
3286
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3287
|
+
| `toggled` | callback `(bool)` | | The state after the click (self-toggle) or the state asked for. |
|
|
3288
|
+
| `clicked` | callback `()` | | |
|
|
3289
|
+
| `toggle` | function `()` | | What a click does; also Space and the screen reader's default action. |
|
|
3290
|
+
|
|
3291
|
+
**`RadioMark`**: The 12px radio dot alone, for rows a plugin lays out itself, such as the palette list with its previews. `hover` lights the ring; `focused` draws the focus ring.
|
|
3292
|
+
|
|
3293
|
+
| Member | Kind | Default | Note |
|
|
3294
|
+
| --- | --- | --- | --- |
|
|
3295
|
+
| `checked` | in `bool` | | |
|
|
3296
|
+
| `hover` | in `bool` | | |
|
|
3297
|
+
| `enabled` | in `bool` | `true` | |
|
|
3298
|
+
| `focused` | in `bool` | | |
|
|
3299
|
+
|
|
3300
|
+
**`Radio`**: One radio choice: the mark, 4px, an 11px label. The plugin owns the state: `checked` is what it shows, a click or Space asks with `clicked`. For a set of choices from one list use RadioGroup, which also takes the arrow keys. (`focusable` and `focused` are RadioGroup's: its radios are one Tab stop, the group's.)
|
|
3301
|
+
|
|
3302
|
+
| Member | Kind | Default | Note |
|
|
3303
|
+
| --- | --- | --- | --- |
|
|
3304
|
+
| `text` | in `string` | | |
|
|
3305
|
+
| `checked` | in `bool` | | |
|
|
3306
|
+
| `enabled` | in `bool` | `true` | |
|
|
3307
|
+
| `focusable` | in `bool` | `true` | |
|
|
3308
|
+
| `focused` | in `bool` | | |
|
|
3309
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3310
|
+
| `clicked` | callback `()` | | |
|
|
3311
|
+
|
|
3312
|
+
**`RadioGroup`**: Radio choices from a list of labels, in a row (12px apart) or a column (`vertical`). One Tab stop; a click, or the arrow keys while the group has the focus (they wrap around), pick one: `current-index` changes and `selected` reports it, as std-widgets' RadioGroup.
|
|
3313
|
+
|
|
3314
|
+
| Member | Kind | Default | Note |
|
|
3315
|
+
| --- | --- | --- | --- |
|
|
3316
|
+
| `model` | in `[string]` | | |
|
|
3317
|
+
| `current-index` | in-out `int` | `-1` | |
|
|
3318
|
+
| `vertical` | in `bool` | | |
|
|
3319
|
+
| `enabled` | in `bool` | `true` | |
|
|
3320
|
+
| `spacing` | in `length` | `root.vertical ? 0px : 12px` | |
|
|
3321
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3322
|
+
| `selected` | callback `(int)` | | |
|
|
3323
|
+
| `select` | function `(index: int)` | | |
|
|
3324
|
+
|
|
3325
|
+
**`Tab`**: One page tab: at least 74px, 12px label, dimmed unless `selected`; the selected tab has a 28px gradient stroke under its label, the others a rounded plate on hover. `emphasis` draws an unselected label in the accent colour (something waits there). Tabs lays them out; use Tab alone only for a strip with other content.
|
|
3326
|
+
|
|
3327
|
+
| Member | Kind | Default | Note |
|
|
3328
|
+
| --- | --- | --- | --- |
|
|
3329
|
+
| `text` | in `string` | | |
|
|
3330
|
+
| `selected` | in `bool` | | |
|
|
3331
|
+
| `emphasis` | in `bool` | | |
|
|
3332
|
+
| `focused` | in `bool` | | |
|
|
3333
|
+
| `clicked` | callback `()` | | |
|
|
3334
|
+
|
|
3335
|
+
**`Tabs`**: A 31px tab strip on `bg-bar` (the picture, audio and properties windows): one Tab per label, left-aligned. A click, or Left/Right/Home/End while the strip has the focus, picks a tab: `current-index` changes and `selected` reports it. The pages are the plugin's (show the one for current-index). Its min-width is the tabs' width, for a window that must fit them.
|
|
3336
|
+
|
|
3337
|
+
| Member | Kind | Default | Note |
|
|
3338
|
+
| --- | --- | --- | --- |
|
|
3339
|
+
| `model` | in `[string]` | | |
|
|
3340
|
+
| `current-index` | in-out `int` | `0` | |
|
|
3341
|
+
| `emphasis` | in `[bool]` | | Per tab (same order as `model`, may be shorter): Tab's `emphasis`, the accent label of a tab where something waits ("Updates (2)"). |
|
|
3342
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3343
|
+
| `selected` | callback `(int)` | | |
|
|
3344
|
+
| `select` | function `(index: int)` | | |
|
|
3345
|
+
|
|
3346
|
+
**`Choice`**: Compact 21px drop-down: the current label (11px, elided) and a small V; a click opens the list below it (Slint keeps the popup inside the window, moving it up when there is no room below). The list is a ListView (rows are created only while visible, so long lists such as output devices or plugin ids stay cheap) of at most `max-visible-rows` 20px rows; it is at least `popup-min-width` wide. Slint moves the popup into the window but does not shorten it: in a window shorter than the list, set `window-height: root.height` (the window's height) and the list is cut to fit, scrolling to reach every row. A Choice in a mounted panel or settings page cannot see its window's height; keep `max-visible-rows` small there. Keyboard: closed and focused (Tab or `focus()`), Up/Down/Home/End change the choice, Space, Enter, F4 or Alt+Down open the list; open, the arrows, Page Up/Down and Home/End move the lit row, Enter or Space picks it, Escape or a click outside closes the list without a change. A pick sets `current-index` and `selected` reports it (only a change), as std-widgets' ComboBox; the pick replaces a one-way binding, so bind `current-index` two-way or set it again in a `changed` handler. The labels are what the list shows: give translated text (@tr in the .slint file, or text values `plugin.i18n.text` from the plugin).
|
|
3347
|
+
|
|
3348
|
+
| Member | Kind | Default | Note |
|
|
3349
|
+
| --- | --- | --- | --- |
|
|
3350
|
+
| `model` | in `[string]` | | |
|
|
3351
|
+
| `current-index` | in-out `int` | `0` | |
|
|
3352
|
+
| `enabled` | in `bool` | `true` | |
|
|
3353
|
+
| `max-visible-rows` | in `int` | `10` | |
|
|
3354
|
+
| `popup-min-width` | in `length` | `0px` | |
|
|
3355
|
+
| `window-height` | in `length` | `0px` | The window's height, when known: the list is never taller. |
|
|
3356
|
+
| `has-focus` | out `bool` | `keys.has-focus` | |
|
|
3357
|
+
| `open` | out `bool` | `popup.is-open` | |
|
|
3358
|
+
| `current-text` | out `string` | `root.current-index >= 0 && root.current-index < root.model.length ? root.model[root.current-index] : ""` | |
|
|
3359
|
+
| `selected` | callback `(int)` | | |
|
|
3360
|
+
| `select` | function `(index: int)` | | |
|
|
3361
|
+
| `show-list` | function `()` | | |
|
|
3362
|
+
| `close-list` | function `()` | | |
|
|
3363
|
+
|
|
3271
3364
|
A component's public functions are called from your own Slint code, through an element id: `row.focus-input()` puts the keyboard focus into `UrlInputRow`'s input (call it when the window opens, for example from `init`), `row.select-all()` selects its text.
|
|
3272
3365
|
|
|
3273
3366
|
#### Markdown
|
|
@@ -3283,7 +3376,7 @@ A component's public functions are called from your own Slint code, through an e
|
|
|
3283
3376
|
| `link-clicked` | callback `(string)` | | |
|
|
3284
3377
|
| `copy-code` | callback `(string)` | | |
|
|
3285
3378
|
|
|
3286
|
-
**`MarkdownView`**: A scrolling Markdown document.
|
|
3379
|
+
**`MarkdownView`**: A scrolling Markdown document: a `ListView` of its blocks, so only the blocks in view exist (a long document no longer builds every block). It draws as `MarkdownBlocks` in a column with `inset` padding would: `inset` above the first block, below the last and at both sides, 8px between.
|
|
3287
3380
|
|
|
3288
3381
|
| Member | Kind | Default | Note |
|
|
3289
3382
|
| --- | --- | --- | --- |
|
|
@@ -3775,14 +3868,18 @@ There is no cleanup function to return from `activate`. Because every resource i
|
|
|
3775
3868
|
A plugin's state is plain variables. The SDK keeps the player in step with it through **bindings** and the **update pass**:
|
|
3776
3869
|
|
|
3777
3870
|
- A binding is a function the SDK calls to get a current value: view.bind(() => props) for a window, panel or settings page, and the function form of a command's `enabled`, `checked`, `busy` and `reason` (Bindable).
|
|
3778
|
-
- In an update pass the SDK calls
|
|
3871
|
+
- In an update pass the SDK calls the bindings of the plugin, compares each result with the value it last sent, and sends only what changed.
|
|
3779
3872
|
|
|
3780
|
-
|
|
3873
|
+
A **full pass** calls every binding. It runs:
|
|
3781
3874
|
|
|
3782
3875
|
- after every handler the SDK calls returns (a command's `run`, a view callback, an `onChange`, `onStatus`, `onEnd` or `onStop` listener), and again when the promise it returned settles;
|
|
3783
|
-
- after the
|
|
3876
|
+
- after the subtitle, window, panel position, timeline, appearance, filter or export state changes;
|
|
3784
3877
|
- when the plugin calls plugin.update().
|
|
3785
3878
|
|
|
3879
|
+
A **precise pass** follows a change of the player state (SDK 1.5.45). While media plays the player sends its state about ten times a second, mostly with only `position` changed. The SDK notes which fields of `plugin.player.state` each binding read the last time it ran (each `view.bind` function on its own; a command's four functions together) and calls only the bindings that read a field that changed. A panel whose list reads nothing of the player state is not rebuilt on every position tick; a label that shows the position is. `player.isCurrent` and `Media.current` count as reading the source. A `player.onChange` listener is followed by a full pass, as above, unless it returned synchronously and read only fields that did not change.
|
|
3880
|
+
|
|
3881
|
+
A precise pass cannot see state the plugin changed by itself. So while precise passes leave bindings out, a full pass follows at least once a second: a variable your own timer changed without `plugin.update()` appears within about a second (before SDK 1.5.45 it usually appeared at the next position tick). Call `plugin.update()` after such a change to show it at once.
|
|
3882
|
+
|
|
3786
3883
|
Several requests within one microtask are merged into one pass, and every target has at most one write in flight: values that change meanwhile are merged into the next write. A binding that changes 60 times a second therefore costs one write per round trip, never a growing queue.
|
|
3787
3884
|
|
|
3788
3885
|
One click, end to end (a bookmark window; the video is at 42 s):
|
|
@@ -3802,7 +3899,7 @@ sequenceDiagram
|
|
|
3802
3899
|
S->>U: the label changes, Jump to bookmark is enabled
|
|
3803
3900
|
```
|
|
3804
3901
|
|
|
3805
|
-
The rule that follows: **state you change outside a handler, or after an `await` inside one, appears only at the next pass.** Changing a variable from your own `setInterval`, a socket callback or the middle of a long task shows nothing until something triggers a
|
|
3902
|
+
The rule that follows: **state you change outside a handler, or after an `await` inside one, appears only at the next full pass.** Changing a variable from your own `setInterval`, a socket callback or the middle of a long task shows nothing until something triggers one (at most about a second while media plays); call `plugin.update()` after such a change. Keep a slider responsive while dragging shows the pattern; How Slint and TypeScript talk explains `set` versus `bind` for views.
|
|
3806
3903
|
|
|
3807
3904
|
#### Stopping
|
|
3808
3905
|
|
|
@@ -3918,7 +4015,7 @@ const toggle = plugin.commands.add({
|
|
|
3918
4015
|
|
|
3919
4016
|
- **The id is the command's identity.** It is unique within the plugin; the player knows the command as `<manifest id>/<id>`. The user's own key bindings are stored under that pair, so changing a title or moving the command to another menu keeps them.
|
|
3920
4017
|
- **`path` puts it in the main menu.** Leave it out for a command that only has bindings or lives in a panel's context menu (Panel.showMenu); it then needs a `title`.
|
|
3921
|
-
- **State is declared, not pushed.** `enabled`, `checked`, `busy` and `reason` are values or functions (Bindable); `enabled` and `checked` may also be conditions from `plugin.when` (below). Functions are evaluated in every update pass and sent only when they change, so the menu's check mark follows the player without a listener you write. Command.set replaces them later.
|
|
4018
|
+
- **State is declared, not pushed.** `enabled`, `checked`, `busy` and `reason` are values or functions (Bindable); `enabled` and `checked` may also be conditions from `plugin.when` (below). Functions are evaluated in every full update pass, and after a player state change only if they read a field that changed; they are sent only when they change, so the menu's check mark follows the player without a listener you write. Command.set replaces them later.
|
|
3922
4019
|
- **The player calls `run` only when it can run.** While a command is disabled or busy, neither its menu item nor its binding calls `run`; `reason` tells the user why (it appears in the menu and, for a key press, on the video).
|
|
3923
4020
|
- **Titles and paths are fixed.** They cannot change in place. A menu that follows changing content (tracks, presets, a radio group) lives in a child scope that is disposed and rebuilt when the content changes; `order` keeps its place (Handles and scopes).
|
|
3924
4021
|
- Disposing the command removes it from the menu and the binding table; the same id can be added again at once. The number of commands per plugin and per player is limited (Reference: limits); the limit stops runaway menus, so cap menus built from media content (tracks, chapters) yourself.
|
|
@@ -4175,7 +4272,7 @@ player.openStream (capability `producer.av`) makes a source whose picture and so
|
|
|
4175
4272
|
|
|
4176
4273
|
#### Next and previous
|
|
4177
4274
|
|
|
4178
|
-
The player's **Previous** and **Next** (menu, keys, the bottom bar) are not tied to a playlist. One plugin at a time can **provide the sequence** with provideSequence, giving `next()`, `previous()` and two flags, `canNext` and `canPrevious`, which are bindings re-evaluated in every update pass.
|
|
4275
|
+
The player's **Previous** and **Next** (menu, keys, the bottom bar) are not tied to a playlist. One plugin at a time can **provide the sequence** with provideSequence, giving `next()`, `previous()` and two flags, `canNext` and `canPrevious`, which are bindings re-evaluated in every full update pass, and after a player state change only if they read a changed field.
|
|
4179
4276
|
|
|
4180
4277
|
- Every plugin sees the flags as `player.state.can.next` and `can.previous`, and player.next() asks the provider to move; the provider's `next` runs in its own scope.
|
|
4181
4278
|
- A second provider fails with `conflict` and does not fail activation; it is tried again when its flags change, so it takes over once the first provider goes away.
|
|
@@ -4536,15 +4633,22 @@ export component ListPanel inherits Rectangle {
|
|
|
4536
4633
|
|
|
4537
4634
|
| | `set({ x })` | `bind(() => ({ x }))` |
|
|
4538
4635
|
| ---------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
4539
|
-
| When it is sent | Immediately (in the next write). | In every update pass, if `x` differs from the last value sent.
|
|
4636
|
+
| When it is sent | Immediately (in the next write). | In every full update pass, and after a player state change if the function read a changed field; when `x` differs from the last value sent. |
|
|
4540
4637
|
| Same value again | Sent again. Use this to pull a control back to the model value after the user moved it. | Not sent. A field the user edited stays as the user left it until your value changes. |
|
|
4541
4638
|
| Write fails | Retried with back-off (0.25 s doubling up to 30 s). | Not repeated until the value changes. |
|
|
4542
4639
|
| Typical use | One-off values: a document, a reset. | Everything derived from plugin state: labels, enabled flags, lists, status lines. |
|
|
4543
4640
|
|
|
4544
|
-
Update passes run after every handler the SDK calls, after player and other state changes, and when you call plugin.update(); several requests are merged into one pass, and each view has at most one write in flight (The update pass has the details).
|
|
4641
|
+
Update passes run after every handler the SDK calls, after player and other state changes, and when you call plugin.update(); several requests are merged into one pass, and each view has at most one write in flight. After a player state change only the bind functions that read a changed field of `plugin.player.state` run again (The update pass has the details).
|
|
4545
4642
|
|
|
4546
4643
|
The rule to remember: **state you change after an `await` inside a handler appears only when the handler finishes**, unless something else triggers a pass. Call `plugin.update()` right after changing it (see Keep a slider responsive while dragging).
|
|
4547
4644
|
|
|
4645
|
+
##### Arrays: only the changed rows are sent
|
|
4646
|
+
|
|
4647
|
+
From player 0.6.0 and SDK 1.5.44, a changed array (a list's rows) is sent as the rows that changed: rows set, inserted, removed or moved, matched by a `key`, `id`, `rowId` or `row-id` field when every row has a unique one, else by position. The player writes those rows into the list Slint already shows, so selecting one row of a long list costs one row, not the whole list. Nothing changes in your code, and the TestHost's `props` still hold the whole array. Two consequences:
|
|
4648
|
+
|
|
4649
|
+
- **Your Slint code must not write into the rows of an array property** (`rows[i].selected = true` in a handler). A whole write used to overwrite such a change on the next update; a row patch touches only the rows whose value changed in your plugin, so the edited row keeps the Slint-side value until your value for that row changes. Send the event to the plugin with a callback and change the row there.
|
|
4650
|
+
- **`set()` with an unchanged array sends the whole array**, as `set()` always sends: use it to pull a list back to your rows. A bound array whose rows did not change sends nothing.
|
|
4651
|
+
|
|
4548
4652
|
##### Two-way controls
|
|
4549
4653
|
|
|
4550
4654
|
A control whose value both the user and the plugin change (a slider, a switch, a text field, a combo box) needs a **two-way binding to an `in-out` property**:
|
|
@@ -5712,6 +5816,8 @@ Every SDK release's CHANGELOG entry opens with who must change and what happens
|
|
|
5712
5816
|
|
|
5713
5817
|
| Release | Who must change | Guide |
|
|
5714
5818
|
| --- | --- | --- |
|
|
5819
|
+
| [1.5.45](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1545) | **Nobody.** No changes to the TypeScript API, `apiVersion` stays `vestra/1`, no new capability, no player version needed. | — |
|
|
5820
|
+
| [1.5.44](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1544) | **Nobody.** No changes to the TypeScript API, `apiVersion` stays `vestra/1`, no new capability. | — |
|
|
5715
5821
|
| [1.5.43](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1543) | **Nobody.** Additions only, `apiVersion` stays `vestra/1`, no new capability. | — |
|
|
5716
5822
|
| [1.5.42](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1542) | **Nobody.** Documentation only: no API changes, `apiVersion` stays `vestra/1`, no player version needed. | — |
|
|
5717
5823
|
| [1.5.41](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md#1541) | **Nobody.** No API changes, `apiVersion` stays `vestra/1`, no player version needed. | — |
|
|
@@ -6268,8 +6374,10 @@ interface CommandState {
|
|
|
6268
6374
|
|
|
6269
6375
|
### Bindable
|
|
6270
6376
|
|
|
6271
|
-
A value, or a function the SDK re-evaluates on every update pass
|
|
6272
|
-
|
|
6377
|
+
A value, or a function the SDK re-evaluates on every full update pass,
|
|
6378
|
+
and after a player state change only if it read a field of
|
|
6379
|
+
`plugin.player.state` that changed (see Scope.update); only
|
|
6380
|
+
changes are sent to the player.
|
|
6273
6381
|
|
|
6274
6382
|
```ts
|
|
6275
6383
|
type Bindable<T> = T | (() => T);
|
|
@@ -6278,9 +6386,9 @@ type Bindable<T> = T | (() => T);
|
|
|
6278
6386
|
Example: Mix a computed state with a fixed one
|
|
6279
6387
|
|
|
6280
6388
|
The Loop item carries a check mark while a loop is set and is always
|
|
6281
|
-
enabled. A function is evaluated again in every update pass (after
|
|
6282
|
-
handler and player state change
|
|
6283
|
-
value never changes.
|
|
6389
|
+
enabled. A function is evaluated again in every full update pass (after
|
|
6390
|
+
each handler), and after a player state change only if it read a changed
|
|
6391
|
+
field; only changes are sent, and a plain value never changes.
|
|
6284
6392
|
```ts
|
|
6285
6393
|
plugin.commands.add({
|
|
6286
6394
|
id: "loop",
|
|
@@ -6701,8 +6809,9 @@ from the menu and the binding table; the id can be added again right away.
|
|
|
6701
6809
|
|
|
6702
6810
|
The handle of one menu item and its keys. Its state (`enabled`, `checked`,
|
|
6703
6811
|
`busy`, `reason`) is bound: functions given to Commands.add or
|
|
6704
|
-
Command.set are evaluated again in every update pass
|
|
6705
|
-
|
|
6812
|
+
Command.set are evaluated again in every full update pass (after
|
|
6813
|
+
a player state change only if they read a changed field of
|
|
6814
|
+
`plugin.player.state`) and only changes reach the player. Its title, path, bindings and button cannot
|
|
6706
6815
|
change in place; dispose the command (or a child Scope) and add it
|
|
6707
6816
|
again. Disposing it, its scope or stopping the plugin removes the menu
|
|
6708
6817
|
item, its keys and its bar button.
|
|
@@ -6765,7 +6874,7 @@ const exportCommand = plugin.commands.add({
|
|
|
6765
6874
|
path: ["@video", "Export…"],
|
|
6766
6875
|
run: runExport,
|
|
6767
6876
|
});
|
|
6768
|
-
// A function: re-evaluated in every
|
|
6877
|
+
// A function: re-evaluated in every full pass and when `phase` changes.
|
|
6769
6878
|
exportCommand.set({ enabled: () => plugin.player.state.phase === "ready" });
|
|
6770
6879
|
```
|
|
6771
6880
|
|
|
@@ -7316,8 +7425,9 @@ a property back: a value the user changes reaches it only through a
|
|
|
7316
7425
|
callback.
|
|
7317
7426
|
|
|
7318
7427
|
`bind` is the usual way: the SDK re-evaluates bound functions in every
|
|
7319
|
-
update pass (after each handler it calls,
|
|
7320
|
-
|
|
7428
|
+
full update pass (after each handler it calls, on Scope.update),
|
|
7429
|
+
and after a player state change those that read a changed field of
|
|
7430
|
+
`plugin.player.state`, and sends only what changed, at most one write in
|
|
7321
7431
|
flight per view. `set` sends now, even an unchanged value.
|
|
7322
7432
|
|
|
7323
7433
|
Writes that fail (a name the Slint file does not declare, a value of the
|
|
@@ -7420,7 +7530,11 @@ interface View {
|
|
|
7420
7530
|
* Keep properties equal to a function of the plugin's state.
|
|
7421
7531
|
*
|
|
7422
7532
|
* The SDK calls `props` in every update pass (and right after `bind`) and
|
|
7423
|
-
* sends the values that differ from the ones it sent last.
|
|
7533
|
+
* sends the values that differ from the ones it sent last. After a change
|
|
7534
|
+
* of the player state alone it calls `props` only if it read a field of
|
|
7535
|
+
* `plugin.player.state` that changed (SDK 1.5.45); a full pass follows
|
|
7536
|
+
* after each handler, on Scope.update and at least once a second
|
|
7537
|
+
* while the player state keeps changing. Because an
|
|
7424
7538
|
* unchanged value is not sent again, text the user typed into an `in-out`
|
|
7425
7539
|
* property stays until the plugin's value for it changes.
|
|
7426
7540
|
*
|
|
@@ -7438,8 +7552,9 @@ interface View {
|
|
|
7438
7552
|
* @example Show what is playing and whether it can seek
|
|
7439
7553
|
* The status window always shows the current file and enables its seek
|
|
7440
7554
|
* controls only for seekable sources. The SDK calls the function right
|
|
7441
|
-
* away
|
|
7442
|
-
*
|
|
7555
|
+
* away, again in every full update pass (after each handler) and after
|
|
7556
|
+
* each change of `source` or `can`, and sends only the values that
|
|
7557
|
+
* changed.
|
|
7443
7558
|
* ```ts
|
|
7444
7559
|
* const status = plugin.windows.create("ui/status.slint");
|
|
7445
7560
|
* status.bind(() => ({
|
|
@@ -10848,8 +10963,8 @@ interface Player {
|
|
|
10848
10963
|
* @example Drive the player's Next and Previous from a playlist
|
|
10849
10964
|
* The player's Next and Previous items and keys play the plugin's list.
|
|
10850
10965
|
* The player calls `next` and `previous` when the user asks; `canNext`
|
|
10851
|
-
* and `canPrevious` are evaluated again after every handler the SDK runs
|
|
10852
|
-
* and while they return false the player disables its items ("No next
|
|
10966
|
+
* and `canPrevious` are evaluated again after every handler the SDK runs
|
|
10967
|
+
* (and after a player state change if they read a changed field), and while they return false the player disables its items ("No next
|
|
10853
10968
|
* item").
|
|
10854
10969
|
* ```ts
|
|
10855
10970
|
* const playAt = async (at: number) => {
|
|
@@ -16902,6 +17017,13 @@ class TestHost implements Transport {
|
|
|
16902
17017
|
* or parsing a file, a child process, a network request) is not waited
|
|
16903
17018
|
* for: on a loaded machine it can finish after `settle()` returns. Poll
|
|
16904
17019
|
* the result you assert on instead (second example).
|
|
17020
|
+
*
|
|
17021
|
+
* `settle()` does not wait for the SDK's once-a-second full update pass
|
|
17022
|
+
* (SDK 1.5.45). After `setSource` (or another player state change) only
|
|
17023
|
+
* the bindings that read a changed field of `plugin.player.state` run
|
|
17024
|
+
* again; state the plugin changed outside a handler without
|
|
17025
|
+
* `plugin.update()` shows only in the next full pass. Have the plugin
|
|
17026
|
+
* call `plugin.update()` after such a change, or wait `settle(1000)`.
|
|
16905
17027
|
* @param ms - Extra time to wait, for debounces and timers (default 0).
|
|
16906
17028
|
* @capability None.
|
|
16907
17029
|
* @throws Never.
|
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.45: 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 (the player's built-in plugins share one). 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:
|
|
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: L2218–2291.
|
|
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/onChange
|
|
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: L2292–2352.
|
|
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: L1861–
|
|
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: L1861–2021; TestHost: L15479.
|
|
84
84
|
|
|
85
85
|
**Command line**
|
|
86
86
|
|
|
@@ -108,40 +108,40 @@ 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 L5952.
|
|
112
112
|
|
|
113
113
|
## Reference
|
|
114
114
|
|
|
115
115
|
- [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; pages: [site](https://vestrasys.github.io/vestra-player-sdk/).
|
|
116
116
|
- Tutorials: Getting started L9–134; Add a window L135–213; Add settings and translations L214–367; Test, check, pack and publish L368–424; Write your own blur kernel L425–685
|
|
117
|
-
- How-to: Control playback L686–854; Store a plugin's data L855–961; Load subtitles from a plugin L962–1074; Keep a slider responsive while dragging L1075–1262; Remember a window's state between runs L1263–1414; Offer a GPU rendering switch L1415–1540; Keep a command's state without keeping the plugin running L1541–1697; Translate a plugin L1698–1860; Test a plugin L1861–
|
|
118
|
-
- Reference: Manifest
|
|
119
|
-
- Explanation: How a plugin runs
|
|
120
|
-
- Guidelines: Plugin rules
|
|
121
|
-
- Upgrading: Migrate a plugin to English source text
|
|
122
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html):
|
|
123
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html):
|
|
117
|
+
- How-to: Control playback L686–854; Store a plugin's data L855–961; Load subtitles from a plugin L962–1074; Keep a slider responsive while dragging L1075–1262; Remember a window's state between runs L1263–1414; Offer a GPU rendering switch L1415–1540; Keep a command's state without keeping the plugin running L1541–1697; Translate a plugin L1698–1860; Test a plugin L1861–2021; Publish a plugin L2022–2123; Troubleshooting L2124–2217
|
|
118
|
+
- Reference: Manifest L2218–2291; Capabilities L2292–2352; Limits L2353–2498; Commands, menus and bindings L2499–3024; Platforms L3025–3055; Theme L3056–3405; Command line L3406–3463; Error codes L3464–3511; Translations L3512–3604; Test host L3605–3791
|
|
119
|
+
- Explanation: How a plugin runs L3792–3994; Commands, menus and key bindings L3995–4149; Playback state, controls and sources L4150–4309; Video and audio processing L4310–4430; Windows, panels, settings pages, dialogs and the OSD L4431–4693; GPU and software rendering L4694–4710; The player's Rust core L4711–5052; How the player runs and talks to plugins L5053–5392
|
|
120
|
+
- Guidelines: Plugin rules L5393–5404; Interface guidelines L5405–5521; Menu guidelines L5522–5580
|
|
121
|
+
- Upgrading: Migrate a plugin to English source text L5581–5730; Migrating off package rollback (1.5.16, removed in 1.5.17) L5731–5808; Who must change, release by release L5809–5874
|
|
122
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L5875–15152
|
|
123
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L15153–18027
|
|
124
124
|
|
|
125
125
|
## API
|
|
126
126
|
|
|
127
127
|
Where each `plugin.<module>` is declared:
|
|
128
128
|
|
|
129
|
-
- Plugin
|
|
130
|
-
- Commands
|
|
129
|
+
- Plugin L6291, Scope L6027, PluginError L5952, Disposable L5890
|
|
130
|
+
- Commands L6881; When L6478; Windows L7824; Panels L9270; Dialogs L9940; Player L10681; video: VideoFilter L11561, VideoView L11661, RenderOptions L11748, VideoShaders L11826, VideoUpscaling L12184; audio: AudioFilter L11606; Subtitles L12420; media: MediaCapture L12824; Resolvers L13054; Packages L13456; Osd L11426; Host L11268; Clipboard L13785; Power L13872; Tools L13931; Storage L14083; System L14282; Secrets L14362; window: MainWindow L9587; App L9767; Rendering L14625; Bindings L13684; Ui L7170; Timeline L9499; Drops L8517; Appearance L8598; I18n L8831; Settings L9114.
|
|
131
131
|
|
|
132
132
|
## Examples
|
|
133
133
|
|
|
134
134
|
Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
|
|
135
135
|
|
|
136
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
137
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
138
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
139
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
140
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
141
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
142
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
143
|
-
- markdown: A window that shows a Markdown document.
|
|
144
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
136
|
+
- hello: The README Quick start: one menu command and an on-screen message. L18462–18557
|
|
137
|
+
- blur: A tool window with one slider that blurs the playing video. L18558–18825
|
|
138
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L18826–19164
|
|
139
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L19165–19357
|
|
140
|
+
- panel-menu: A sidebar panel with its own right-click menu. L19358–19572
|
|
141
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L19573–19738
|
|
142
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L19739–19908
|
|
143
|
+
- markdown: A window that shows a Markdown document. L19909–20111
|
|
144
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L20112–20285
|
|
145
145
|
|
|
146
146
|
## Optional
|
|
147
147
|
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// row (a chat message: a `[MarkdownBlock]` field of the row's struct).
|
|
10
10
|
// `code-copy: true` adds a Copy button to code blocks that reports
|
|
11
11
|
// `copy-code(code)`; the plugin copies it (`plugin.clipboard`).
|
|
12
|
-
import {
|
|
12
|
+
import { ListView } from "std-widgets.slint";
|
|
13
13
|
import { Theme } from "theme.slint";
|
|
14
14
|
import { Icon } from "icons.slint";
|
|
15
15
|
|
|
@@ -234,7 +234,10 @@ export component MarkdownBlocks inherits Rectangle {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
// A scrolling Markdown document
|
|
237
|
+
// A scrolling Markdown document: a `ListView` of its blocks, so only the
|
|
238
|
+
// blocks in view exist (a long document no longer builds every block). It
|
|
239
|
+
// draws as `MarkdownBlocks` in a column with `inset` padding would: `inset`
|
|
240
|
+
// above the first block, below the last and at both sides, 8px between.
|
|
238
241
|
export component MarkdownView inherits Rectangle {
|
|
239
242
|
in property <[MarkdownBlock]> blocks;
|
|
240
243
|
// Space around the document.
|
|
@@ -243,17 +246,20 @@ export component MarkdownView inherits Rectangle {
|
|
|
243
246
|
in property <bool> code-copy: false;
|
|
244
247
|
callback link-clicked(string);
|
|
245
248
|
callback copy-code(string);
|
|
246
|
-
|
|
249
|
+
list := ListView {
|
|
247
250
|
width: 100%;
|
|
248
251
|
height: 100%;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
for block[i] in root.blocks: Rectangle {
|
|
253
|
+
property <length> top: i == 0 ? root.inset : 0px;
|
|
254
|
+
height: self.top + view.preferred-height
|
|
255
|
+
+ (i == root.blocks.length - 1 ? root.inset : 8px);
|
|
256
|
+
view := MarkdownBlockView {
|
|
257
|
+
x: root.inset;
|
|
258
|
+
y: parent.top;
|
|
259
|
+
width: max(0px, parent.width - 2 * root.inset);
|
|
260
|
+
height: self.preferred-height;
|
|
261
|
+
block: block;
|
|
262
|
+
available: self.width;
|
|
257
263
|
code-copy: root.code-copy;
|
|
258
264
|
link-clicked(url) => { root.link-clicked(url); }
|
|
259
265
|
copy-code(code) => { root.copy-code(code); }
|