@vestra-player/sdk 1.5.18 → 1.5.21
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 +29 -0
- package/dist/capabilities.js +1 -0
- package/dist/commands.js +2 -3
- package/dist/player-runtime.d.ts +1 -1
- package/dist/player-runtime.js +1 -1
- package/dist/plugin/markdown.d.ts +7 -0
- package/dist/plugin/markdown.js +62 -22
- package/dist/plugin/packages.js +5 -0
- package/dist/plugin/player.d.ts +2 -0
- package/dist/plugin/player.js +11 -6
- package/dist/plugin/services.js +11 -3
- package/dist/plugin/testing.d.ts +47 -8
- package/dist/plugin/testing.js +123 -20
- package/dist/plugin/types.d.ts +183 -25
- package/dist/plugin/ui.d.ts +4 -2
- package/dist/plugin/ui.js +31 -13
- package/dist/plugin/values.d.ts +15 -0
- package/dist/plugin/values.js +18 -0
- package/dist/wire.d.ts +3 -2
- package/dist/wire.js +3 -2
- package/llms-full.txt +176 -30
- package/llms.txt +23 -23
- package/package.json +1 -1
- package/slint/vestra/icons.slint +3 -0
- package/slint/vestra/widgets.slint +19 -30
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.21: 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
|
|
|
@@ -445,7 +445,7 @@ Key names describe keys, not characters, with two deliberate exceptions:
|
|
|
445
445
|
- **Option combinations match the physical key**, so `Alt+O` fires for ⌥O although that key types `ø`, and dead keys (⌥E, ⌥N…) fire too. On layouts that need Option to type a symbol (German ⌥5 = ``, ⌥L = `@`), a binding for the typed symbol (`"["`, `"@"`) wins over one for the physical key (`Alt+5`).
|
|
446
446
|
- **`Ctrl` is portable, `Cmd` is not.** On macOS `Ctrl+X` fires for both ⌃X and ⌘X, `Cmd+X` only for ⌘X. On Windows `Cmd` is the Windows logo key, which the system mostly keeps, so write `Ctrl+…` for a binding that works on both systems.
|
|
447
447
|
|
|
448
|
-
Mouse bindings apply only over the video area. `Mouse:LeftDoubleClick:LeftHalf` and `:RightHalf` win over the plain double click; wheel bindings count accumulated scroll steps. The right button is reserved for the context menu, and a left drag in window mode moves the window instead of clicking. A `Mouse:LeftClick` fires on release, except when the same modifiers also have a double-click binding and the click's command is not checkable: then the player waits the system's double-click interval to tell a click from a double click. (A checkable command such as play/pause runs at once; a double click runs it twice, which restores its state, and the double-click binding still fires.)
|
|
448
|
+
Mouse bindings apply only over the video area. `Mouse:LeftDoubleClick:LeftHalf` and `:RightHalf` win over the plain double click; wheel bindings count accumulated scroll steps. The right button is reserved for the context menu, and a left drag in window mode moves the window instead of clicking. A `Mouse:LeftClick` fires on release, except when the same modifiers also have a double-click binding and the click's command is not checkable: then the player waits the system's double-click interval to tell a click from a double click. (A checkable command such as play/pause runs at once; a double click runs it twice, which restores its state, and the double-click binding still fires.) The player's defaults (player 0.5.2): a left click on the video is Play/Pause and nothing is bound to a double click (up to 0.5.1 a double click was Toggle Fullscreen); a click whose command is disabled does nothing.
|
|
449
449
|
|
|
450
450
|
##### The user's bindings
|
|
451
451
|
|
|
@@ -579,10 +579,27 @@ The player's **Previous** and **Next** (menu, keys, the bottom bar) are not tied
|
|
|
579
579
|
|
|
580
580
|
#### Core actions
|
|
581
581
|
|
|
582
|
-
Play/pause, stop, previous/next, frame steps, relative seeks, speed, volume and mute are **core actions**: commands the player registers itself, so they work with no plugin running. plugin.host.run(action) runs one exactly as its menu item or key would, with the same checks and the same on-screen text. A plugin's play button should call `host.run("play-pause")` rather than `setPaused`, so that it behaves like the Space key in every case (restart after the end, the "Paused" message, disabled without a source).
|
|
582
|
+
Play/pause, stop, previous/next, frame steps, relative seeks, speed, volume and mute are **core actions**: commands the player registers itself, so they work with no plugin running. plugin.host.run(action) runs one exactly as its menu item or key would, with the same checks and the same on-screen text. A plugin's play button should call `host.run("play-pause")` rather than `setPaused`, so that it behaves like the Space key in every case (restart after the end, the "Paused" message, disabled without a source). A fullscreen button calls `host.run("fullscreen")` (player 0.5.2), which toggles the main window like Enter; show the button's state from `window.state.fullscreen`.
|
|
583
583
|
|
|
584
584
|
The difference from the `Player` methods: a `Player` control is a precise operation the plugin is responsible for (conditional, silent, its own error handling); a core action is the user's action, performed on the plugin's behalf. The action ids, menu places and default keys are in Reference: commands; how keys reach them is in Commands, menus and key bindings.
|
|
585
585
|
|
|
586
|
+
##### Step seeks: exact or keyframe
|
|
587
|
+
|
|
588
|
+
← / → (`seek-backward` / `seek-forward`) seek by the step that player.setStepSeek sets (5 s by default). By default they land on the exact time, which on a file with keyframes far apart means decoding from the previous keyframe first: a visible stall on 4K video. `setStepSeek({ seconds, keyframes: true })` (player 0.5.1, SDK 1.5.19) makes them land on a keyframe instead: forward on the first keyframe at or after position + step, backward on the last at or before position − step, and every press moves. Presses during a seek add up and continue from where it landed, and the on-screen text shows the landed time. Sources without moving video still seek exactly. The seek bar, `player.seek`, scrubs, chapters, loops and `player.step` are never affected.
|
|
589
|
+
|
|
590
|
+
For more steps of your own (Ctrl+→ and the like), call `plugin.host.run("seek-forward", { seconds: 30 })`: the same checks, mode, accumulation and text as →. In keyframe mode it resolves once the seek has landed, so `player.state.position` is current afterwards; do not show your own text for it. Set `"minPlayerVersion": "0.5.1"` when you rely on either option: older players ignore them.
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
const keyframes = true; // the plugin's own setting
|
|
594
|
+
await plugin.player.setStepSeek({ seconds: 5, keyframes });
|
|
595
|
+
const seek = (delta: number) =>
|
|
596
|
+
plugin.host.run(delta < 0 ? "seek-backward" : "seek-forward", {
|
|
597
|
+
seconds: Math.abs(delta),
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
In tests, `TestHost.keyframes` models the source's keyframes, and `host.coreSeeks` records each step seek with where it landed.
|
|
602
|
+
|
|
586
603
|
## Concept: Video and audio processing
|
|
587
604
|
|
|
588
605
|
A plugin can change what the user sees and hears in three different places. They look alike in the API (each is a handle with `set`, a `status` and `onStatus`), but they act at different stages and have different owners, so choosing the right one matters:
|
|
@@ -856,7 +873,8 @@ export component ListPanel inherits Rectangle {
|
|
|
856
873
|
##### Names and values
|
|
857
874
|
|
|
858
875
|
- Write names in camelCase or kebab-case in TypeScript: the SDK turns every capital letter into `-` plus the lower-case letter (`canMark` → `can-mark`, `strengthChanged` → `strength-changed`). Fields inside struct values are **not** converted, so spell struct fields exactly as the Slint struct does (and without `_`, which Slint treats as `-`).
|
|
859
|
-
- Values must be JSON: booleans, numbers, strings, arrays (Slint models) and objects (Slint structs). Two exceptions: an image from ui.image
|
|
876
|
+
- Values must be JSON: booleans, numbers, strings, arrays (Slint models) and objects (Slint structs). Two exceptions: an image from ui.image or ui.remoteImage wherever the Slint type is `image`: the whole value, or (SDK 1.5.19, player 0.5.1) a field at any depth, such as the logo of each row of a list; and a document from ui.markdown wherever the Slint type is `[MarkdownBlock]`: the whole value, or (SDK 1.5.4, the player after 0.4.0) a field at any depth, such as the `body` of each message in a chat list.
|
|
877
|
+
- Images in rows: give small pictures `maxSide` (about twice the drawn size, `64` for a 32 px logo), so that the player decodes them small. The images nested in one view's values may show at most 32 MB of decoded pixels together; a write past that is refused with `limit`. A nested image whose file fails the player's checks is drawn empty, and the rest of the row is shown. Rows with images update in place, so a `ListView` keeps its scroll position when the list is written again.
|
|
860
878
|
- Every name must exist in the Slint file. Writing a property the file does not declare makes a tool window's `show()` or a panel's `ready` fail, and in a settings page it keeps the Options window from opening; for a tool window, so does a callback bound with `on()` that the file does not declare. Once the view is open, such writes are reported to onError.
|
|
861
879
|
|
|
862
880
|
##### set, bind and the update pass
|
|
@@ -2157,7 +2175,8 @@ The table lists the API whose documentation names a player version, newest last:
|
|
|
2157
2175
|
| 0.4.1 | PluginError, FileResolverOptions, PackageCandidate.install, PluginWarning.code, Packages.validate |
|
|
2158
2176
|
| 0.4.2 | Resolvers.fileTypes, Resolvers.onFileTypesChange |
|
|
2159
2177
|
| 0.4.3 | CommandOptions.path, PropValue, View.set, Appearance.setLanguage, TemplateArgs, Text, ConfirmOptions.args, OsdPanel.set, OsdPanelOptions.maxColumns, Osd.show |
|
|
2160
|
-
| 0.5.1 | SlotState.reason, Panel.setInteraction, PlaybackStats.presentedFps |
|
|
2178
|
+
| 0.5.1 | ImageRef, ImageOptions.maxSide, PropValue, Ui.image, Ui.remoteImage, SlotState.reason, Panel.setInteraction, PlaybackStats.presentedFps, Player.setStepSeek, Host.run, Packages.reload |
|
|
2179
|
+
| 0.5.2 | HostAction |
|
|
2161
2180
|
|
|
2162
2181
|
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).
|
|
2163
2182
|
|
|
@@ -2178,7 +2197,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
|
|
|
2178
2197
|
| `ui.window` | Windows.create | — | Open tool windows | |
|
|
2179
2198
|
| `ui.mount` | Panels.mount, Panels.bottom, Panels.sidebar, Panels.backdrop, PanelSlot.onChange, Panel.onChange, BackdropSlot.onChange | — | Show panels in the bottom bar, the sidebar or behind the video | |
|
|
2180
2199
|
| `ui.dialog` | Dialogs.confirm, Dialogs.openFile, Dialogs.openFiles, Dialogs.openDirectory, Dialogs.saveFile | — | Show file pickers and confirmations | |
|
|
2181
|
-
| `plugins.manage` | Packages.validate, Packages.validateArchive, Packages.setEnabled, Packages.uninstall | — | Install, uninstall and disable other plugins | yes |
|
|
2200
|
+
| `plugins.manage` | Packages.validate, Packages.validateArchive, Packages.setEnabled, Packages.reload, Packages.uninstall | — | Install, uninstall and disable other plugins | yes |
|
|
2182
2201
|
| `processing.video` | Plugin.video.filter, Plugin.video.view, Plugin.video.render | `playback.read` | Process the picture (filters, shaders) | |
|
|
2183
2202
|
| `processing.audio` | Plugin.audio.filter | `playback.read`, `events` | Process the sound | |
|
|
2184
2203
|
| `subtitles.read` | Subtitles.state, Subtitles.onChange, Subtitles.refresh | — | Read subtitles | |
|
|
@@ -2392,7 +2411,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
|
|
|
2392
2411
|
| Copy to Clipboard ▸ File Name | | `vestra.playback/copy-name` |
|
|
2393
2412
|
| Copy to Clipboard ▸ Folder Path | | `vestra.playback/copy-folder` |
|
|
2394
2413
|
| Copy to Clipboard ▸ Full Path | | `vestra.playback/copy-path` |
|
|
2395
|
-
| Playback ▸ Play/Pause | `Space` | `host/play-pause` |
|
|
2414
|
+
| Playback ▸ Play/Pause | `Space`, `Mouse:LeftClick` | `host/play-pause` |
|
|
2396
2415
|
| Playback ▸ Previous | `PageUp`, `Mouse:X1` | `host/previous` |
|
|
2397
2416
|
| Playback ▸ Next | `PageDown`, `Mouse:X2` | `host/next` |
|
|
2398
2417
|
| Playback ▸ Seek ▸ Previous Frame | `D` | `host/frame-back` |
|
|
@@ -2622,7 +2641,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
|
|
|
2622
2641
|
| Emulator ▸ Open NES ROM… | | `vestra.nes/nes.open` |
|
|
2623
2642
|
| Emulator ▸ Stop NES | | `vestra.nes/nes.stop` |
|
|
2624
2643
|
| Emulator ▸ NES Tools… | | `vestra.nes/nes.window` |
|
|
2625
|
-
| Toggle Fullscreen | `Enter`, `Alt+Enter`, `F11
|
|
2644
|
+
| Toggle Fullscreen | `Enter`, `Alt+Enter`, `F11` | `host/fullscreen` |
|
|
2626
2645
|
| Screen ▸ Picture-in-Picture | | `vestra.window/pip` |
|
|
2627
2646
|
| Screen ▸ Mini Size | ``` | `vestra.window/mini` |
|
|
2628
2647
|
| Screen ▸ 0.5× | `1` | `vestra.window/size.0.5` |
|
|
@@ -2651,7 +2670,6 @@ Every command the player (`host`) and its built-in plugins register when the pla
|
|
|
2651
2670
|
| Settings ▸ Keyboard Shortcuts… | | `vestra.hotkeys/keys` |
|
|
2652
2671
|
| Settings ▸ Mouse… | | `vestra.hotkeys/mouse` |
|
|
2653
2672
|
| Settings ▸ Ignore Default Shortcuts | | `vestra.hotkeys/ignore-defaults` |
|
|
2654
|
-
| Settings ▸ Plugin Manager… | | `vestra.plugins/manage` |
|
|
2655
2673
|
| Settings ▸ Appearance ▸ Vestra Dark | | `vestra.preferences/theme.vestra` |
|
|
2656
2674
|
| Settings ▸ Appearance ▸ Vestra Light | | `vestra.preferences/theme.vestra-light` |
|
|
2657
2675
|
| Settings ▸ Appearance ▸ Show Logo When Idle | | `vestra.visualizer/idle-logo` |
|
|
@@ -2736,7 +2754,7 @@ The player's own playback commands, which plugin.host.run runs by id (capability
|
|
|
2736
2754
|
| Action | Menu item | Default bindings |
|
|
2737
2755
|
| --- | --- | --- |
|
|
2738
2756
|
| `stop` | Close | `F4` |
|
|
2739
|
-
| `play-pause` | Playback ▸ Play/Pause | `Space` |
|
|
2757
|
+
| `play-pause` | Playback ▸ Play/Pause | `Space`, `Mouse:LeftClick` |
|
|
2740
2758
|
| `previous` | Playback ▸ Previous | `PageUp`, `Mouse:X1` |
|
|
2741
2759
|
| `next` | Playback ▸ Next | `PageDown`, `Mouse:X2` |
|
|
2742
2760
|
| `frame-back` | Playback ▸ Seek ▸ Previous Frame | `D` |
|
|
@@ -2749,6 +2767,7 @@ The player's own playback commands, which plugin.host.run runs by id (capability
|
|
|
2749
2767
|
| `volume-up` | Audio ▸ Volume + | `ArrowUp`, `Mouse:WheelUp` |
|
|
2750
2768
|
| `volume-down` | Audio ▸ Volume − | `ArrowDown`, `Mouse:WheelDown` |
|
|
2751
2769
|
| `mute` | Audio ▸ Mute | `M` |
|
|
2770
|
+
| `fullscreen` | Toggle Fullscreen | `Enter`, `Alt+Enter`, `F11` |
|
|
2752
2771
|
|
|
2753
2772
|
The player also owns **Toggle Fullscreen** (`host/fullscreen`), **Leave Fullscreen** (`host/exit-fullscreen`, `Escape`, only in fullscreen) and **Quit** (`host/quit`); their keys are in Built-in commands.
|
|
2754
2773
|
|
|
@@ -2923,17 +2942,19 @@ Icon { name: "close"; width: 20px; height: 20px; } // scaled
|
|
|
2923
2942
|
| `add` | 10 × 10 | 1.5px |
|
|
2924
2943
|
| `remove` | 10 × 10 | 1.5px |
|
|
2925
2944
|
| `sort` | 10 × 10 | 1.5px |
|
|
2945
|
+
| `fullscreen` | 14 × 12 | 1.5px |
|
|
2946
|
+
| `exit-fullscreen` | 14 × 12 | 1.5px |
|
|
2926
2947
|
|
|
2927
2948
|
#### Widgets
|
|
2928
2949
|
|
|
2929
2950
|
`@vestra/widgets.slint`: compact controls drawn with the theme. Other controls come from `std-widgets.slint`.
|
|
2930
2951
|
|
|
2931
|
-
**`ToolTipArea`**: Hover for
|
|
2952
|
+
**`ToolTipArea`**: Hover for about half a second to show `text` beside the pointer. The tip closes when the pointer leaves, or presses on content that does not take the press itself; it shows again on the next hover. It is Slint's input-transparent tooltip: every press reaches the control under the pointer, and the tip does not take the keyboard focus. (Up to SDK 1.5.20 the tip was a PopupWindow, which swallowed the first press after it showed and took the focus while it was open.)
|
|
2932
2953
|
|
|
2933
2954
|
| Member | Kind | Default | Note |
|
|
2934
2955
|
| --- | --- | --- | --- |
|
|
2935
2956
|
| `text` | in `string` | | |
|
|
2936
|
-
| `showing` | out `bool` | | The tip is showing (read-only). |
|
|
2957
|
+
| `showing` | out `bool` | | The tip is showing (read-only); it follows the tip's delay closely, not exactly. |
|
|
2937
2958
|
|
|
2938
2959
|
**`Button`**: Dialog button: text (21px, or 18px when `small`), or an icon button when `text` is empty and `icon` holds Path data drawn in an icon-width × icon-height viewbox (filled, or 1.5px round-capped lines when `stroked`, as the @vestra/icons.slint set). The caller sizes icon buttons.
|
|
2939
2960
|
|
|
@@ -3537,6 +3558,20 @@ interface ImageRef {
|
|
|
3537
3558
|
}
|
|
3538
3559
|
```
|
|
3539
3560
|
|
|
3561
|
+
### ImageOptions
|
|
3562
|
+
|
|
3563
|
+
Options of Ui.image and Ui.remoteImage.
|
|
3564
|
+
|
|
3565
|
+
```ts
|
|
3566
|
+
interface ImageOptions {
|
|
3567
|
+
/**
|
|
3568
|
+
* Decode the image scaled down, aspect kept, so that neither side is over this many pixels (an integer 1–4096); smaller images stay as they are. Use about twice the drawn size (64 for a 32 px logo) so that it stays sharp on high-density screens. It keeps the decoded memory small: a 1024×1024 icon is 4 MB decoded, 16 KB at `maxSide: 64`.
|
|
3569
|
+
* @player 0.5.1; older players refuse the reference (`invalid`).
|
|
3570
|
+
*/
|
|
3571
|
+
maxSide?: number;
|
|
3572
|
+
}
|
|
3573
|
+
```
|
|
3574
|
+
|
|
3540
3575
|
### MarkdownRef
|
|
3541
3576
|
|
|
3542
3577
|
A Markdown document for `MarkdownView` or `MarkdownBlocks`, made by
|
|
@@ -3571,9 +3606,8 @@ interface MarkdownOptions {
|
|
|
3571
3606
|
|
|
3572
3607
|
### PropValue
|
|
3573
3608
|
|
|
3574
|
-
The value of one Slint property: JSON with Markdown documents
|
|
3575
|
-
depth (PropData)
|
|
3576
|
-
property.
|
|
3609
|
+
The value of one Slint property: JSON with Markdown documents and images
|
|
3610
|
+
at any depth (PropData).
|
|
3577
3611
|
|
|
3578
3612
|
```ts
|
|
3579
3613
|
type PropValue = PropData | ImageRef;
|
|
@@ -3581,11 +3615,11 @@ type PropValue = PropData | ImageRef;
|
|
|
3581
3615
|
|
|
3582
3616
|
### PropData
|
|
3583
3617
|
|
|
3584
|
-
JSON whose values may also be Markdown documents
|
|
3585
|
-
of PropValue that nests.
|
|
3618
|
+
JSON whose values may also be Markdown documents and (SDK 1.5.19) images,
|
|
3619
|
+
at any depth: the part of PropValue that nests.
|
|
3586
3620
|
|
|
3587
3621
|
```ts
|
|
3588
|
-
type PropData = null | boolean | number | string | MarkdownRef | Text | TextList | PropData[] | {
|
|
3622
|
+
type PropData = null | boolean | number | string | MarkdownRef | ImageRef | Text | TextList | PropData[] | {
|
|
3589
3623
|
[key: string]: PropData;
|
|
3590
3624
|
};
|
|
3591
3625
|
```
|
|
@@ -3611,10 +3645,22 @@ interface Ui {
|
|
|
3611
3645
|
* Reference a PNG or JPEG file for an `image` property.
|
|
3612
3646
|
* @capability None.
|
|
3613
3647
|
* @param path - Absolute path of the image file, inside the package directory or the data directory.
|
|
3648
|
+
* @param options - `maxSide` (SDK 1.5.19, player 0.5.1): decode the image scaled down, aspect kept, so that neither side is over it.
|
|
3614
3649
|
* @returns A new ImageRef, owned by this scope.
|
|
3615
|
-
* @throws invalid (throws) `path` is not a non-empty string.
|
|
3650
|
+
* @throws invalid (throws) `path` is not a non-empty string, or `maxSide` is not an integer 1–4096.
|
|
3616
3651
|
*/
|
|
3617
|
-
image(path: string): ImageRef;
|
|
3652
|
+
image(path: string, options?: ImageOptions): ImageRef;
|
|
3653
|
+
/**
|
|
3654
|
+
* Download an `https:` PNG or JPEG image (a thumbnail, a repository's plugin icon) and reference the local copy, as Ui.image does.
|
|
3655
|
+
* @capability None (the plugin process has network access).
|
|
3656
|
+
* @player Any (the reference is an ordinary ImageRef); nested in a value, player 0.5.1.
|
|
3657
|
+
* @param url - The image's `https:` address.
|
|
3658
|
+
* @param options - As for Ui.image.
|
|
3659
|
+
* @returns The reference, owned by this scope, once the file is in the cache.
|
|
3660
|
+
* @throws invalid (rejects) `url` is not an `https:` address, or `maxSide` is not an integer 1–4096.
|
|
3661
|
+
* @throws unavailable (rejects) the download failed, timed out, was too large, or is not a PNG or JPEG image; or the plugin has no data directory.
|
|
3662
|
+
*/
|
|
3663
|
+
remoteImage(url: string, options?: ImageOptions): Promise<ImageRef>;
|
|
3618
3664
|
/**
|
|
3619
3665
|
* Prepare a Markdown document for `MarkdownView` (`@vestra/markdown.slint`).
|
|
3620
3666
|
* @capability None.
|
|
@@ -5181,10 +5227,28 @@ interface Player {
|
|
|
5181
5227
|
*/
|
|
5182
5228
|
step(unit: "frame" | "keyframe", direction: "backward" | "forward", options?: Conditional): Promise<void>;
|
|
5183
5229
|
/**
|
|
5184
|
-
* The core's first-tier relative seek (`seek-backward` / `seek-forward`,
|
|
5230
|
+
* The core's first-tier relative seek (`seek-backward` / `seek-forward`,
|
|
5231
|
+
* ← / →): 0.1–3600 seconds in tenths, default 5. The newest call of any
|
|
5232
|
+
* plugin wins; it reverts when that plugin stops.
|
|
5233
|
+
*
|
|
5234
|
+
* `keyframes: true` (player 0.5.1, SDK 1.5.19): the step seeks
|
|
5235
|
+
* (`seek-backward` / `seek-forward`, including Host.run with
|
|
5236
|
+
* `seconds` and the picture-in-picture skips) land on a keyframe instead
|
|
5237
|
+
* of the exact time: forward on the first keyframe at or after position +
|
|
5238
|
+
* step, backward on the last at or before position − step, and they
|
|
5239
|
+
* always move. Presses during a seek add up and continue from where it
|
|
5240
|
+
* landed; the on-screen text shows the landed time. Sources without moving
|
|
5241
|
+
* video seek exactly. The seek bar, Player.seek, scrubs, chapters,
|
|
5242
|
+
* loops and Player.step are unaffected. Omitted or `false`: exact
|
|
5243
|
+
* seeks, as before. `seconds` and `keyframes` are written as a pair: the
|
|
5244
|
+
* newest call sets both.
|
|
5245
|
+
*
|
|
5246
|
+
* Older players ignore `keyframes`: set `"minPlayerVersion": "0.5.1"` in
|
|
5247
|
+
* the manifest when you rely on it.
|
|
5185
5248
|
*/
|
|
5186
5249
|
setStepSeek(options: {
|
|
5187
5250
|
seconds: number;
|
|
5251
|
+
keyframes?: boolean;
|
|
5188
5252
|
}): Promise<void>;
|
|
5189
5253
|
/** The chapters of the current media (empty without media or chapters), read on demand; `sourceId` tells which source they belong to. `playback.read`. */
|
|
5190
5254
|
chapters(): Promise<{
|
|
@@ -5218,23 +5282,29 @@ interface Player {
|
|
|
5218
5282
|
|
|
5219
5283
|
### HostAction
|
|
5220
5284
|
|
|
5221
|
-
The core's basic playback actions (menu commands owned by the player)
|
|
5285
|
+
The core's basic playback actions (menu commands owned by the player),
|
|
5286
|
+
and the main window's fullscreen toggle.
|
|
5222
5287
|
|
|
5223
5288
|
| action | default bindings | behaviour |
|
|
5224
5289
|
| --- | --- | --- |
|
|
5225
5290
|
| `stop` | F4 | close the source |
|
|
5226
|
-
| `play-pause` | Space | pause / resume, replay after the end |
|
|
5291
|
+
| `play-pause` | Space, a left click on the video (player 0.5.2) | pause / resume, replay after the end |
|
|
5227
5292
|
| `previous` / `next` | PageUp, Mouse:X1 / PageDown, Mouse:X2 | ask the sequence provider |
|
|
5228
5293
|
| `frame-back` / `frame-forward` | D / F | `player.step("frame", …)` |
|
|
5229
|
-
| `seek-backward` / `seek-forward` | ← / → | relative seek by `setStepSeek` seconds |
|
|
5294
|
+
| `seek-backward` / `seek-forward` | ← / → | relative seek by `setStepSeek` seconds (or `host.run`'s `seconds`), exact or to keyframes as `setStepSeek` says |
|
|
5230
5295
|
| `rate-normal`, `rate-down`, `rate-up` | Z, X, C | 1×, ±0.1 within 0.25–4 |
|
|
5231
5296
|
| `volume-up` / `volume-down` | ↑ / ↓, wheel | ±5 (`volume-up` stops at 100) |
|
|
5232
5297
|
| `mute` | M | toggle mute |
|
|
5298
|
+
| `fullscreen` | Enter, Alt+Enter, F11 | toggle the main window's fullscreen: enter it when windowed, leave it when fullscreen, as the Toggle Fullscreen command does (SDK 1.5.21, player 0.5.2; `unavailable` when the player has no window) |
|
|
5233
5299
|
|
|
5234
|
-
In the binding overlay their keys are `host/<action>`.
|
|
5300
|
+
In the binding overlay their keys are `host/<action>`. `fullscreen`
|
|
5301
|
+
changes no playback state: read the result from `window.state.fullscreen`
|
|
5302
|
+
(`plugin.window.onChange`), which also changes when the user toggles it.
|
|
5303
|
+
Players before 0.5.2 reject it with `invalid`: set
|
|
5304
|
+
`"minPlayerVersion": "0.5.2"` when you use it.
|
|
5235
5305
|
|
|
5236
5306
|
```ts
|
|
5237
|
-
type HostAction = "play-pause" | "stop" | "previous" | "next" | "frame-back" | "frame-forward" | "seek-backward" | "seek-forward" | "rate-down" | "rate-up" | "rate-normal" | "volume-up" | "volume-down" | "mute";
|
|
5307
|
+
type HostAction = "play-pause" | "stop" | "previous" | "next" | "frame-back" | "frame-forward" | "seek-backward" | "seek-forward" | "rate-down" | "rate-up" | "rate-normal" | "volume-up" | "volume-down" | "mute" | "fullscreen";
|
|
5238
5308
|
```
|
|
5239
5309
|
|
|
5240
5310
|
### Host
|
|
@@ -5252,6 +5322,23 @@ interface Host {
|
|
|
5252
5322
|
* Prefer this over reimplementing playback buttons.
|
|
5253
5323
|
*/
|
|
5254
5324
|
run(action: HostAction): Promise<void>;
|
|
5325
|
+
/**
|
|
5326
|
+
* One step seek of `seconds` (0.1–3600 in tenths) instead of the
|
|
5327
|
+
* Player.setStepSeek step: the same checks, mode (exact or
|
|
5328
|
+
* keyframes), accumulation and on-screen text as ← / →. Use it for more
|
|
5329
|
+
* seek steps of your own (Ctrl+→ and the like) so that they add up with
|
|
5330
|
+
* the core's and land the same way.
|
|
5331
|
+
*
|
|
5332
|
+
* In keyframe mode it resolves once the seek has landed (so
|
|
5333
|
+
* `player.state.position` is current after the `await`); presses that add
|
|
5334
|
+
* up resolve together, the last with the text. In exact mode it resolves
|
|
5335
|
+
* once the seek is submitted, as before. `invalid` for `seconds` out of
|
|
5336
|
+
* range.
|
|
5337
|
+
* @player 0.5.1 (set `"minPlayerVersion": "0.5.1"`: older players ignore `seconds` and seek by the `setStepSeek` step)
|
|
5338
|
+
*/
|
|
5339
|
+
run(action: "seek-backward" | "seek-forward", options: {
|
|
5340
|
+
seconds: number;
|
|
5341
|
+
}): Promise<void>;
|
|
5255
5342
|
}
|
|
5256
5343
|
```
|
|
5257
5344
|
|
|
@@ -6092,6 +6179,23 @@ interface Packages {
|
|
|
6092
6179
|
* Stop or start a plugin (`plugins.manage`). An installed plugin stays disabled across launches; a development load only for this run. `invalid` to disable oneself, `missing` unknown. `busy` while it is being installed. `unsupported` to disable a built-in (PluginInfo.builtin): users manage third-party plugins only (players released with SDK 1.5.18 on; older ones disabled built-ins too).
|
|
6093
6180
|
*/
|
|
6094
6181
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
6182
|
+
/**
|
|
6183
|
+
* Restart a plugin (`plugins.manage`): stop it and start it again from the
|
|
6184
|
+
* same package with the same data directory, for example to recover one
|
|
6185
|
+
* that misbehaves. Built-ins included. A stopped or failed plugin is just
|
|
6186
|
+
* started. The disabled flag is never written, so a reload never leaves
|
|
6187
|
+
* a plugin disabled.
|
|
6188
|
+
*
|
|
6189
|
+
* Resolves once the plugin is stopped and its start is scheduled; the
|
|
6190
|
+
* player starts it as soon as the old process is gone (at most 20 s).
|
|
6191
|
+
* Follow it with Packages.onChange: `starting`, then `running`,
|
|
6192
|
+
* or `failed` with the reason.
|
|
6193
|
+
*
|
|
6194
|
+
* `invalid` for oneself or a disabled plugin (enable it instead),
|
|
6195
|
+
* `missing` unknown, `busy` while it is being installed or reloaded.
|
|
6196
|
+
* @player 0.5.1; older players reject it as an unknown request (`unsupported`).
|
|
6197
|
+
*/
|
|
6198
|
+
reload(id: string): Promise<void>;
|
|
6095
6199
|
/**
|
|
6096
6200
|
* Remove an installed plugin (`plugins.manage`); `removeData` also deletes its data directory, otherwise reinstalling it from the same source reconnects to its data. Its user bindings stay (as orphans). `unsupported` for built-ins and development loads, `missing` unknown, `invalid` for oneself.
|
|
6097
6201
|
*/
|
|
@@ -6991,12 +7095,16 @@ class TestHost implements Transport {
|
|
|
6991
7095
|
kind: string;
|
|
6992
7096
|
title?: string | null;
|
|
6993
7097
|
}[]): void;
|
|
6994
|
-
/**
|
|
7098
|
+
/**
|
|
7099
|
+
* Runtime images written to views (`plugin.ui.image`, `plugin.ui.remoteImage`), whole values and (SDK 1.5.19) images nested in rows, newest last. The SDK refuses images of released scopes before anything is sent. The test host does not read the files, so it does not check the player's per-view budget (`IMAGE_LIMITS.viewBytes`).
|
|
7100
|
+
*/
|
|
6995
7101
|
readonly images: {
|
|
6996
7102
|
/** The image file's path, as given to `ui.image`. */
|
|
6997
7103
|
path: string;
|
|
6998
7104
|
/** The SDK's reference number for that `ImageRef`. */
|
|
6999
7105
|
ref: number;
|
|
7106
|
+
/** Its `maxSide` option, when given. */
|
|
7107
|
+
maxSide?: number;
|
|
7000
7108
|
}[];
|
|
7001
7109
|
/**
|
|
7002
7110
|
* The file an image property shows, or `null` when the value is not a runtime image. View properties hold images in their wire form; this reads the path back.
|
|
@@ -7317,6 +7425,25 @@ class TestHost implements Transport {
|
|
|
7317
7425
|
constructor();
|
|
7318
7426
|
/** The `player.setStepSeek` value in effect (default 5). */
|
|
7319
7427
|
stepSeek: number;
|
|
7428
|
+
/** The `player.setStepSeek` `keyframes` in effect (default `false`, SDK 1.5.19). */
|
|
7429
|
+
stepSeekKeyframes: boolean;
|
|
7430
|
+
/**
|
|
7431
|
+
* Test data: the keyframe times (seconds, ascending) of the current source, for keyframe step seeks. `null` (the default): none modelled, and step seeks are exact even with `stepSeekKeyframes`.
|
|
7432
|
+
*/
|
|
7433
|
+
keyframes: number[] | null;
|
|
7434
|
+
/**
|
|
7435
|
+
* Every step seek the core ran (`seek-backward` / `seek-forward`, from a key, the menu or `host.run`), newest last: the step, whether it went to a keyframe, and where it landed.
|
|
7436
|
+
*/
|
|
7437
|
+
readonly coreSeeks: {
|
|
7438
|
+
/** `seek-backward` or `seek-forward`. */
|
|
7439
|
+
action: string;
|
|
7440
|
+
/** The step: `host.run`'s `seconds`, else `setStepSeek`'s. */
|
|
7441
|
+
seconds: number;
|
|
7442
|
+
/** It landed on a keyframe (`stepSeekKeyframes` and keyframes modelled). */
|
|
7443
|
+
keyframes: boolean;
|
|
7444
|
+
/** The new position. */
|
|
7445
|
+
landed: number;
|
|
7446
|
+
}[];
|
|
7320
7447
|
/** Every `player.step`, newest last. */
|
|
7321
7448
|
readonly steps: {
|
|
7322
7449
|
/** `"frame"` or `"keyframe"`. */
|
|
@@ -7430,7 +7557,7 @@ class TestHost implements Transport {
|
|
|
7430
7557
|
/** Every `packages.setEnabled` / `uninstall` / `install`, newest last. */
|
|
7431
7558
|
readonly packageCalls: {
|
|
7432
7559
|
/** Which call. */
|
|
7433
|
-
method: "setEnabled" | "uninstall" | "install";
|
|
7560
|
+
method: "setEnabled" | "reload" | "uninstall" | "install";
|
|
7434
7561
|
/** The target plugin id. */
|
|
7435
7562
|
id: string;
|
|
7436
7563
|
/** `setEnabled`'s value. */
|
|
@@ -7447,7 +7574,7 @@ class TestHost implements Transport {
|
|
|
7447
7574
|
*/
|
|
7448
7575
|
readonly candidates: Map<string, PackageManifest>;
|
|
7449
7576
|
/**
|
|
7450
|
-
* Remote
|
|
7577
|
+
* Remote images (`plugin.ui.markdown`, `plugin.ui.remoteImage`): address → a local PNG or JPEG file served for it, or `null` for a failing download. Nothing is fetched from the network in tests; unknown addresses fail.
|
|
7451
7578
|
*/
|
|
7452
7579
|
readonly remoteImages: Map<string, string | null>;
|
|
7453
7580
|
/**
|
|
@@ -7876,6 +8003,25 @@ const UI_VALUE_LIMITS: {
|
|
|
7876
8003
|
};
|
|
7877
8004
|
```
|
|
7878
8005
|
|
|
8006
|
+
### IMAGE_LIMITS
|
|
8007
|
+
|
|
8008
|
+
The player's runtime image limits (`host/src/ui_image.rs`): a file's
|
|
8009
|
+
bytes and sides, the decoded pixels each plugin caches, and (SDK 1.5.19,
|
|
8010
|
+
player 0.5.1) the decoded pixels the nested images of one view may show.
|
|
8011
|
+
|
|
8012
|
+
```ts
|
|
8013
|
+
const IMAGE_LIMITS: Readonly<{
|
|
8014
|
+
/** The largest image file the player reads, in bytes. */
|
|
8015
|
+
fileBytes: number;
|
|
8016
|
+
/** The longest side, in pixels, the player accepts for an image file. */
|
|
8017
|
+
side: 4096;
|
|
8018
|
+
/** The decoded pixel bytes the player caches for each plugin's images. */
|
|
8019
|
+
pluginBytes: number;
|
|
8020
|
+
/** The decoded pixel bytes the nested images of one view may show (player 0.5.1). */
|
|
8021
|
+
viewBytes: number;
|
|
8022
|
+
}>;
|
|
8023
|
+
```
|
|
8024
|
+
|
|
7879
8025
|
### HostError
|
|
7880
8026
|
|
|
7881
8027
|
A definite refusal from the player (or the plugin runner), with a stable
|
|
@@ -9594,7 +9740,7 @@ From top to bottom:
|
|
|
9594
9740
|
- **The window.** A fixed `width`; `Theme.bg-panel` instead of a hard-coded colour, so the background follows the player's palette.
|
|
9595
9741
|
- **The properties.** `level` is `in-out` because both the plugin and the user change it; `label` is only written by the plugin. `level-changed` reports a drag; `reset` reports the reset buttons.
|
|
9596
9742
|
- **The layout.** `Theme.dialog-padding` and `Theme.font-body` give the spacing and text size of the player's dialogs.
|
|
9597
|
-
- **`ToolTipArea`** shows its `text` after the pointer rests on its content for 600 ms.
|
|
9743
|
+
- **`ToolTipArea`** shows its `text` after the pointer rests on its content for about half a second (600 ms before player 0.5.2). The tip never takes a press or the keyboard focus: a click goes to the control under the pointer even while the tip shows.
|
|
9598
9744
|
- **The sliders** are bound _two-way_ to `level` with `<=>`, so dragging one moves the other at once, inside Slint. `changed` passes the value to the plugin. A one-way binding (`value: root.level`) would be broken by Slint the first time the user moved the slider. `panel: true` is the panel style; without it, the bar style.
|
|
9599
9745
|
- **`Button`** with `text` is a dialog button; `small: true` is the 18-pixel one.
|
|
9600
9746
|
- **Icon buttons** have no `text`; `icon` is SVG-style path data drawn in an `icon-width` × `icon-height` box (14×12 by default). `stroked: true` draws lines instead of filling. `checked` draws the icon in the accent colour.
|
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.21: 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> are line numbers in llms-full.txt, next to this file: 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: L2120–2184.
|
|
37
37
|
|
|
38
38
|
**main.ts**
|
|
39
39
|
|
|
@@ -60,7 +60,7 @@ export default async function activate(plugin: Plugin) {
|
|
|
60
60
|
- `ui.window`: Windows.create
|
|
61
61
|
- `ui.mount`: Panels.mount/bottom/sidebar/backdrop, PanelSlot/Panel/BackdropSlot.onChange
|
|
62
62
|
- `ui.dialog`: Dialogs.confirm/openFile/openFiles/openDirectory/saveFile
|
|
63
|
-
- `plugins.manage`: Packages.validate/validateArchive/setEnabled/uninstall
|
|
63
|
+
- `plugins.manage`: Packages.validate/validateArchive/setEnabled/reload/uninstall
|
|
64
64
|
- `processing.video`: Plugin.video.filter/video.view/video.render (also `playback.read`)
|
|
65
65
|
- `processing.audio`: Plugin.audio.filter (also `playback.read`, `events`)
|
|
66
66
|
- `subtitles.read`: Subtitles.state/onChange/refresh
|
|
@@ -76,9 +76,9 @@ export default async function activate(plugin: Plugin) {
|
|
|
76
76
|
- `ui.settings`: Settings.page/show
|
|
77
77
|
- `ui.theme`: Appearance.set/setLanguage
|
|
78
78
|
- `secrets`: Secrets.get/set/delete
|
|
79
|
-
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details:
|
|
79
|
+
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2185–2227.
|
|
80
80
|
|
|
81
|
-
**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` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide:
|
|
81
|
+
**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` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1606–1771; TestHost: L6855.
|
|
82
82
|
|
|
83
83
|
**Command line**
|
|
84
84
|
|
|
@@ -106,39 +106,39 @@ export default async function activate(plugin: Plugin) {
|
|
|
106
106
|
- `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
|
|
107
107
|
- `reserved`: The id is `vestra` or starts with `vestra.`
|
|
108
108
|
- `versionMismatch`: The package needs a newer player or another `apiVersion`
|
|
109
|
-
- Others: PluginError, llms-full.txt
|
|
109
|
+
- Others: PluginError, llms-full.txt L3126.
|
|
110
110
|
|
|
111
111
|
## Reference
|
|
112
112
|
|
|
113
113
|
- [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; the pages are on the [site](https://vestrasys.github.io/vestra-player-sdk/).
|
|
114
114
|
- Tutorials: Getting started L9–214
|
|
115
|
-
- Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–
|
|
116
|
-
- How-to: Keep a slider responsive while dragging
|
|
117
|
-
- Guidelines: Interface guidelines
|
|
118
|
-
- Reference: Manifest
|
|
119
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html):
|
|
120
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html):
|
|
115
|
+
- Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–602; Video and audio processing L603–680; Windows, panels, settings pages, dialogs and the OSD L681–968
|
|
116
|
+
- How-to: Keep a slider responsive while dragging L969–1143; Remember a window's state between runs L1144–1273; Translate a plugin L1274–1457; Migrate a plugin to English source text L1458–1605; Test a plugin L1606–1771; Publish a plugin L1772–1860; Migrating off package rollback (1.5.16, removed in 1.5.17) L1861–1936
|
|
117
|
+
- Guidelines: Interface guidelines L1937–2021; Menu guidelines L2022–2119
|
|
118
|
+
- Reference: Manifest L2120–2184; Capabilities L2185–2227; Limits L2228–2322; Commands, menus and bindings L2323–2791; Platforms L2792–2820; Theme L2821–3027; Command line L3028–3083
|
|
119
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3084–6690
|
|
120
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6691–8055
|
|
121
121
|
|
|
122
122
|
## API
|
|
123
123
|
|
|
124
124
|
Where each module of `plugin` (`plugin.<module>`) is declared:
|
|
125
125
|
|
|
126
|
-
- Plugin
|
|
127
|
-
- Commands
|
|
126
|
+
- Plugin L3318, Scope L3187, PluginError L3126, Disposable L3099
|
|
127
|
+
- Commands L3530; Windows L3760; Panels L4403; Dialogs L4689; Player L5137; video: VideoFilter L5443, VideoView L5506, RenderOptions L5548; audio: AudioFilter L5467; Subtitles L5646; media: MediaCapture L5806; Resolvers L5911; Packages L6153; Osd L5379; Host L5310; Clipboard L6285; Power L6305; Tools L6338; Storage L6381; System L6404; Secrets L6422; window: MainWindow L4526; App L4607; Bindings L6255; Ui L3635; Timeline L4483; Drops L4059; Appearance L4088; I18n L4230; Settings L4336.
|
|
128
128
|
|
|
129
129
|
## Examples
|
|
130
130
|
|
|
131
131
|
Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
|
|
132
132
|
|
|
133
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
134
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
135
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
136
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
137
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
138
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
139
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
140
|
-
- markdown: A window that shows a Markdown document.
|
|
141
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
133
|
+
- hello: The README Quick start: one menu command and an on-screen message. L8479–8568
|
|
134
|
+
- blur: A tool window with one slider that blurs the playing video. L8569–8830
|
|
135
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L8831–9163
|
|
136
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9164–9348
|
|
137
|
+
- panel-menu: A sidebar panel with its own right-click menu. L9349–9554
|
|
138
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L9555–9714
|
|
139
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L9715–9878
|
|
140
|
+
- markdown: A window that shows a Markdown document. L9879–10073
|
|
141
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L10074–10241
|
|
142
142
|
|
|
143
143
|
## Optional
|
|
144
144
|
|
package/package.json
CHANGED
package/slint/vestra/icons.slint
CHANGED
|
@@ -45,6 +45,9 @@ export global Icons {
|
|
|
45
45
|
if name == "add" { return "M 5 1.5 V 8.5 M 1.5 5 H 8.5"; }
|
|
46
46
|
if name == "remove" { return "M 1.5 5 H 8.5"; }
|
|
47
47
|
if name == "sort" { return "M 3 1.5 V 8.5 M 1.2 6.7 L 3 8.5 L 4.8 6.7 M 7 8.5 V 1.5 M 5.2 3.3 L 7 1.5 L 8.8 3.3"; }
|
|
48
|
+
// Four corners pointing out (enter) or in (leave), the default 14 × 12 box.
|
|
49
|
+
if name == "fullscreen" { return "M 1.5 4.5 V 1.5 H 4.5 M 9.5 1.5 H 12.5 V 4.5 M 12.5 7.5 V 10.5 H 9.5 M 4.5 10.5 H 1.5 V 7.5"; }
|
|
50
|
+
if name == "exit-fullscreen" { return "M 4.5 1.5 V 4.5 H 1.5 M 9.5 1.5 V 4.5 H 12.5 M 12.5 7.5 H 9.5 V 10.5 M 1.5 7.5 H 4.5 V 10.5"; }
|
|
48
51
|
return "";
|
|
49
52
|
}
|
|
50
53
|
// Line width.
|
|
@@ -5,15 +5,20 @@
|
|
|
5
5
|
import { TextEdit } from "std-widgets.slint";
|
|
6
6
|
import { Theme } from "theme.slint";
|
|
7
7
|
|
|
8
|
-
// Hover for
|
|
9
|
-
// the pointer leaves or presses
|
|
8
|
+
// Hover for about half a second to show `text` beside the pointer. The tip
|
|
9
|
+
// closes when the pointer leaves, or presses on content that does not take
|
|
10
|
+
// the press itself; it shows again on the next hover. It is Slint's
|
|
11
|
+
// input-transparent tooltip: every press reaches the control under the
|
|
12
|
+
// pointer, and the tip does not take the keyboard focus. (Up to SDK 1.5.20 the tip was a
|
|
13
|
+
// PopupWindow, which swallowed the first press after it showed and took the
|
|
14
|
+
// focus while it was open.)
|
|
10
15
|
export component ToolTipArea {
|
|
11
16
|
in property <string> text;
|
|
12
|
-
// The tip is showing (read-only)
|
|
17
|
+
// The tip is showing (read-only); it follows the tip's delay closely,
|
|
18
|
+
// not exactly.
|
|
13
19
|
out property <bool> showing;
|
|
14
|
-
//
|
|
15
|
-
// (Never assign the timer's `running`: that drops its binding
|
|
16
|
-
// tip would show only once.)
|
|
20
|
+
// Pressed during this hover: no tip until the pointer leaves.
|
|
21
|
+
// (Never assign the timer's `running`: that drops its binding.)
|
|
17
22
|
private property <bool> done;
|
|
18
23
|
area := TouchArea {
|
|
19
24
|
width: 100%; height: 100%;
|
|
@@ -22,40 +27,24 @@ export component ToolTipArea {
|
|
|
22
27
|
if !self.has-hover {
|
|
23
28
|
root.done = false;
|
|
24
29
|
root.showing = false;
|
|
25
|
-
tip.close();
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
pointer-event(event) => {
|
|
29
33
|
if event.kind == PointerEventKind.down {
|
|
30
34
|
root.done = true;
|
|
31
35
|
root.showing = false;
|
|
32
|
-
tip.close();
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
triggered => {
|
|
40
|
-
root.done = true;
|
|
41
|
-
root.showing = true;
|
|
42
|
-
tip.show();
|
|
38
|
+
// Removing it closes an open tip.
|
|
39
|
+
if root.text != "" && !root.done: Tooltip {
|
|
40
|
+
// Interpolated text is plain: `*` or `_` in it are not markup.
|
|
41
|
+
text: @markdown("\{root.text}");
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Rectangle {
|
|
50
|
-
background: Theme.bg-panel;
|
|
51
|
-
border-color: Theme.line;
|
|
52
|
-
border-width: 1px;
|
|
53
|
-
border-radius: Theme.radius-control;
|
|
54
|
-
HorizontalLayout {
|
|
55
|
-
padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px;
|
|
56
|
-
Text { text: root.text; color: Theme.text; font-size: Theme.font-body; }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
44
|
+
Timer {
|
|
45
|
+
interval: 500ms;
|
|
46
|
+
running: area.has-hover && root.text != "" && !root.done && !root.showing;
|
|
47
|
+
triggered => { root.showing = true; }
|
|
59
48
|
}
|
|
60
49
|
}
|
|
61
50
|
|