@vestra-player/sdk 1.5.7 → 1.5.9
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 +22 -0
- package/README.md +4 -1
- package/bin/vestra-plugin.mjs +194 -17
- package/dist/commands.d.ts +1 -2
- package/dist/commands.js +1 -2
- package/dist/i18n-tools.d.ts +67 -0
- package/dist/i18n-tools.js +731 -0
- package/dist/i18n.d.ts +130 -0
- package/dist/i18n.js +673 -0
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/appearance.js +27 -1
- package/dist/plugin/language.d.ts +4 -0
- package/dist/plugin/language.js +5 -0
- package/dist/plugin/osd.d.ts +3 -0
- package/dist/plugin/osd.js +45 -9
- package/dist/plugin/packages.js +11 -65
- package/dist/plugin/resolvers.js +64 -0
- package/dist/plugin/runtime.d.ts +3 -1
- package/dist/plugin/runtime.js +6 -0
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -0
- package/dist/plugin/testing.d.ts +155 -4
- package/dist/plugin/testing.js +332 -50
- package/dist/plugin/types.d.ts +247 -46
- package/dist/plugin/ui.js +4 -1
- package/dist/plugin/window.d.ts +4 -0
- package/dist/plugin/window.js +8 -0
- package/dist/wire.d.ts +29 -14
- package/dist/wire.js +12 -12
- package/llms-full.txt +399 -52
- package/llms.txt +44 -40
- package/package.json +1 -1
- package/slint/vestra/markdown.slint +5 -5
- package/slint/vestra/widgets.slint +4 -4
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.9: 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
|
|
|
@@ -124,7 +124,7 @@ A manifest with every field. `id`, `apiVersion`, `entry` and `capabilities` are
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install
|
|
127
|
+
Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install such a package (`reserved`). Every other plugin is a third-party plugin, including those hosted in the official repository.
|
|
128
128
|
|
|
129
129
|
| Capability | Opens |
|
|
130
130
|
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
@@ -998,9 +998,8 @@ change).
|
|
|
998
998
|
| `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
|
|
999
999
|
| `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
|
|
1000
1000
|
| `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
1001
|
-
| `cleanup` | PackageTrial.stop could not confirm cleanup; call it again. |
|
|
1002
1001
|
| `limit` | A player-wide limit was reached (for example the number of commands). |
|
|
1003
|
-
| `reserved` | Packages.validate, PackageCandidate.
|
|
1002
|
+
| `reserved` | Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
|
|
1004
1003
|
| `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
|
|
1005
1004
|
| `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
|
|
1006
1005
|
| `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
|
|
@@ -1080,7 +1079,7 @@ interface Scope extends Disposable {
|
|
|
1080
1079
|
readonly media: MediaCapture;
|
|
1081
1080
|
/** Page-URL to stream-URL resolvers (`source.resolve`). */
|
|
1082
1081
|
readonly resolvers: Resolvers;
|
|
1083
|
-
/** The installed plugins; validation,
|
|
1082
|
+
/** The installed plugins; validation, installs (`plugins.manage` for changes). */
|
|
1084
1083
|
readonly packages: Packages;
|
|
1085
1084
|
/** On-screen messages over the video (`ui.osd`). */
|
|
1086
1085
|
readonly osd: Osd;
|
|
@@ -1110,8 +1109,10 @@ interface Scope extends Disposable {
|
|
|
1110
1109
|
readonly timeline: Timeline;
|
|
1111
1110
|
/** Files dropped on the video area, by extension (`ui.drop`). */
|
|
1112
1111
|
readonly drops: Drops;
|
|
1113
|
-
/** The player's palette. */
|
|
1112
|
+
/** The player's palette and the saved interface language. */
|
|
1114
1113
|
readonly appearance: Appearance;
|
|
1114
|
+
/** The interface language of this run (read-only). */
|
|
1115
|
+
readonly i18n: I18n;
|
|
1115
1116
|
/** Pages in the player's options window (`ui.settings`). */
|
|
1116
1117
|
readonly settings: Settings;
|
|
1117
1118
|
/** A child scope; disposing it releases only what was created through it. The child is itself owned by this scope. */
|
|
@@ -1174,8 +1175,7 @@ interface Plugin extends Scope {
|
|
|
1174
1175
|
readonly id: string;
|
|
1175
1176
|
/**
|
|
1176
1177
|
* A writable directory for this plugin's own files (settings, remembered
|
|
1177
|
-
* folders). Kept across restarts, disabling and upgrades
|
|
1178
|
-
* temporary one. Write files atomically: to a temporary name, then rename.
|
|
1178
|
+
* folders). Kept across restarts, disabling and upgrades. Write files atomically: to a temporary name, then rename.
|
|
1179
1179
|
* Throws `unavailable` when the player provided none.
|
|
1180
1180
|
*
|
|
1181
1181
|
* Location: `~/Library/Application Support/Vestra/plugin-data/<id>/` on
|
|
@@ -1218,9 +1218,9 @@ type Bindable<T> = T | (() => T);
|
|
|
1218
1218
|
### MenuId
|
|
1219
1219
|
|
|
1220
1220
|
A built-in top-level menu of the player, named in a command path as
|
|
1221
|
-
`@<id>` (see CommandOptions.path): Open
|
|
1222
|
-
Subtitles
|
|
1223
|
-
|
|
1221
|
+
`@<id>` or by its English key (see CommandOptions.path): Open,
|
|
1222
|
+
Playback, Subtitles, Video, Audio, Screen, Settings. The list is
|
|
1223
|
+
`MENU_IDS` in `@vestra-player/sdk/testing`.
|
|
1224
1224
|
|
|
1225
1225
|
```ts
|
|
1226
1226
|
type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
|
|
@@ -1241,7 +1241,7 @@ Built-in menus keep their place. The seven built-in top-level menus
|
|
|
1241
1241
|
(MenuId) sit at fixed orders, whatever is inside them: Open 110,
|
|
1242
1242
|
Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings
|
|
1243
1243
|
830 (player 0.3.0). This holds whether the path names the menu by id
|
|
1244
|
-
(`["@video", "Blur"]`) or by its
|
|
1244
|
+
(`["@video", "Blur"]`) or by its key (`["Video", "Blur"]`), so a plugin's
|
|
1245
1245
|
item never moves the player's menu. Directly inside a built-in menu,
|
|
1246
1246
|
`order` 0 (the default) sorts after every other order: an item without
|
|
1247
1247
|
`order` goes at the end, after the player's own items (which all use 100
|
|
@@ -1265,16 +1265,23 @@ interface CommandOptions {
|
|
|
1265
1265
|
* one segment is a top-level item. Omit it for a command that is not in the
|
|
1266
1266
|
* main menu (bindings and panel menus only); `title` is then required.
|
|
1267
1267
|
*
|
|
1268
|
-
* Segments are
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1268
|
+
* Segments are English source text, and menus merge by them: commands
|
|
1269
|
+
* of different plugins with the same leading segments share a submenu.
|
|
1270
|
+
* The player shows each segment and the title in the interface language
|
|
1271
|
+
* from the declaring plugin's catalog (`locales/<lang>.po`, see the
|
|
1272
|
+
* i18n guide); the player's own menus
|
|
1273
|
+
* (Open, Playback, Subtitles, Video, Audio, Screen, Settings, and
|
|
1274
|
+
* Playback ▸ Speed, Playback ▸ Seek) always show the player's words.
|
|
1275
|
+
* A built-in menu can also be named by its id, `@` plus a MenuId:
|
|
1276
|
+
* `@open`, `@playback`, `@subtitles`, `@video`, `@audio`, `@screen` or
|
|
1277
|
+
* `@settings`. Only the first segment is read as an id; an unknown id, or
|
|
1278
|
+
* an id with nothing after it, fails with `invalid`. Players up to 0.4.2
|
|
1279
|
+
* titled their menus in Chinese (视频…): there `["Video", "Blur"]` is a
|
|
1280
|
+
* menu of its own, so plugins for those players write `@video`; player
|
|
1281
|
+
* 0.4.3 still accepts the old Chinese titles of its own menus.
|
|
1275
1282
|
*/
|
|
1276
1283
|
path?: string[];
|
|
1277
|
-
/** Menu label; defaults to the last path segment. */
|
|
1284
|
+
/** Menu label (English source text, shown translated); defaults to the last path segment. */
|
|
1278
1285
|
title?: string;
|
|
1279
1286
|
/**
|
|
1280
1287
|
* Sibling order in the menu, ascending (integer, default 0). A submenu sits where its first command sits, except a built-in menu, which keeps its fixed place. Directly inside a built-in menu, 0 (the default) sorts last. Equal orders: plugin id, then declaration order. Range ±1,000,000. See CommandOptions for the menu order rules.
|
|
@@ -1903,6 +1910,64 @@ interface Appearance {
|
|
|
1903
1910
|
* @throws capability (rejects) The manifest does not declare `ui.theme`.
|
|
1904
1911
|
*/
|
|
1905
1912
|
set(theme: ThemeId): Promise<void>;
|
|
1913
|
+
/** The saved interface language: `"system"` (follow the system's languages, the default) or a Locale. */
|
|
1914
|
+
readonly language: LanguagePreference;
|
|
1915
|
+
/**
|
|
1916
|
+
* Save the interface language for the next start.
|
|
1917
|
+
* @capability ui.theme
|
|
1918
|
+
* @player 0.4.3
|
|
1919
|
+
* @param language - `"system"` or a Locale.
|
|
1920
|
+
* @returns Resolves once the player saved it; Appearance.language has the new value.
|
|
1921
|
+
* @throws invalid (rejects) `language` is not `"system"` or a Locale.
|
|
1922
|
+
* @throws capability (rejects) The manifest does not declare `ui.theme`.
|
|
1923
|
+
* @throws unsupported (rejects) A player before 0.4.3.
|
|
1924
|
+
*/
|
|
1925
|
+
setLanguage(language: LanguagePreference): Promise<void>;
|
|
1926
|
+
}
|
|
1927
|
+
```
|
|
1928
|
+
|
|
1929
|
+
### Locale
|
|
1930
|
+
|
|
1931
|
+
An interface language of the player: English (`en`, the source language
|
|
1932
|
+
of every plugin text) or a translation. The list is `LOCALES` in
|
|
1933
|
+
`@vestra-player/sdk/testing`.
|
|
1934
|
+
|
|
1935
|
+
```ts
|
|
1936
|
+
type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
|
|
1937
|
+
```
|
|
1938
|
+
|
|
1939
|
+
### LanguagePreference
|
|
1940
|
+
|
|
1941
|
+
The saved interface language (Appearance.language): follow the
|
|
1942
|
+
system, or one Locale.
|
|
1943
|
+
|
|
1944
|
+
```ts
|
|
1945
|
+
type LanguagePreference = "system" | Locale;
|
|
1946
|
+
```
|
|
1947
|
+
|
|
1948
|
+
### TemplateArgs
|
|
1949
|
+
|
|
1950
|
+
Arguments of a template sentence (Osd.show,
|
|
1951
|
+
OsdPanel.set, ConfirmOptions.args): an array for `{}` and
|
|
1952
|
+
`{0}`, `{1}` placeholders, or an object for `{name}` placeholders. At most
|
|
1953
|
+
16 strings and finite numbers; whole numbers print without a fraction.
|
|
1954
|
+
|
|
1955
|
+
```ts
|
|
1956
|
+
type TemplateArgs = readonly (string | number)[] | Readonly<Record<string, string | number>>;
|
|
1957
|
+
```
|
|
1958
|
+
|
|
1959
|
+
### I18n
|
|
1960
|
+
|
|
1961
|
+
The interface language of this run. `plugin.i18n`.
|
|
1962
|
+
|
|
1963
|
+
- @capability None.
|
|
1964
|
+
|
|
1965
|
+
```ts
|
|
1966
|
+
interface I18n {
|
|
1967
|
+
/**
|
|
1968
|
+
* The interface language of this run, chosen at start-up from the saved Appearance.language or the system's languages; English when neither names a Locale. Players before 0.4.3 report `"en"`.
|
|
1969
|
+
*/
|
|
1970
|
+
readonly locale: Locale;
|
|
1906
1971
|
}
|
|
1907
1972
|
```
|
|
1908
1973
|
|
|
@@ -2281,6 +2346,10 @@ interface ConfirmOptions {
|
|
|
2281
2346
|
destructive?: boolean;
|
|
2282
2347
|
/** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
|
|
2283
2348
|
checkbox?: string;
|
|
2349
|
+
/**
|
|
2350
|
+
* Values for the placeholders of `message` and `detail` (player 0.4.3). Every text of the dialog is English source text that the player shows translated from the plugin's catalog; `message` and `detail` are templates like Osd.show's. The length limits apply to the shown text.
|
|
2351
|
+
*/
|
|
2352
|
+
args?: TemplateArgs;
|
|
2284
2353
|
}
|
|
2285
2354
|
```
|
|
2286
2355
|
|
|
@@ -2903,9 +2972,10 @@ interface OsdPanel extends Disposable {
|
|
|
2903
2972
|
/**
|
|
2904
2973
|
* Show the block with this text, change it, or hide it.
|
|
2905
2974
|
* @capability ui.osd
|
|
2906
|
-
* @param text - The text, with `\n` between lines: at most 32 lines and 4096 bytes. `null` hides the block.
|
|
2975
|
+
* @param text - The text, with `\n` between lines: at most 32 lines and 4096 bytes once translated and formatted. `null` hides the block. A template, like Osd.show's.
|
|
2976
|
+
* @param args - Values for its placeholders (player 0.4.3).
|
|
2907
2977
|
*/
|
|
2908
|
-
set(text: string | null): void;
|
|
2978
|
+
set(text: string | null, args?: TemplateArgs): void;
|
|
2909
2979
|
}
|
|
2910
2980
|
```
|
|
2911
2981
|
|
|
@@ -2921,13 +2991,14 @@ interface Osd {
|
|
|
2921
2991
|
/**
|
|
2922
2992
|
* Show a one-line message at the top left of the video for 2 seconds.
|
|
2923
2993
|
* @capability ui.osd
|
|
2924
|
-
* @param text - 1–200 characters; line breaks become spaces.
|
|
2994
|
+
* @param text - The template, 1–200 characters once translated and formatted; line breaks become spaces.
|
|
2995
|
+
* @param args - Values for its placeholders (player 0.4.3; older players show the template as it is).
|
|
2925
2996
|
* @returns Resolves once the message is on screen.
|
|
2926
|
-
* @throws invalid (rejects)
|
|
2997
|
+
* @throws invalid (rejects) The shown text is empty, only white space, or longer than 200 characters, or `args` is not a TemplateArgs.
|
|
2927
2998
|
* @throws unavailable (rejects) The player runs without a user interface.
|
|
2928
2999
|
* @throws capability (rejects) The manifest does not declare `ui.osd`.
|
|
2929
3000
|
*/
|
|
2930
|
-
show(text: string): Promise<void>;
|
|
3001
|
+
show(text: string, args?: TemplateArgs): Promise<void>;
|
|
2931
3002
|
/**
|
|
2932
3003
|
* Create this plugin's information block.
|
|
2933
3004
|
* @capability ui.osd
|
|
@@ -3450,6 +3521,20 @@ interface Resolvers {
|
|
|
3450
3521
|
* Register a resolver. When anyone opens a matching URL or file the player calls `resolve`; `signal` aborts when the open is cancelled or replaced. One scheme + host pair, and one extension, has one resolver (`conflict` otherwise). A rejected `resolve` fails the open with the error's message.
|
|
3451
3522
|
*/
|
|
3452
3523
|
add(options: ResolverOptions): Resolver;
|
|
3524
|
+
/**
|
|
3525
|
+
* The extensions some plugin's file resolver handles (SDK 1.5.8, player 0.4.2): lower case, without the dot, sorted, each once; not who registered them. Reading needs no capability; `[]` from older players. Without `events` it is the list from when the plugin first read it. Use it to accept such files beside the player's own media types, for example in a playlist's drop or folder scan.
|
|
3526
|
+
* @capability None.
|
|
3527
|
+
* @player 0.4.2
|
|
3528
|
+
*/
|
|
3529
|
+
readonly fileTypes: readonly string[];
|
|
3530
|
+
/**
|
|
3531
|
+
* Resolvers.fileTypes changed: a file resolver was added, or its plugin stopped. Needs `events`; without it the listener never fires.
|
|
3532
|
+
* @param listener - Receives the new list.
|
|
3533
|
+
* @returns A handle that removes the listener.
|
|
3534
|
+
* @capability `events`
|
|
3535
|
+
* @player 0.4.2
|
|
3536
|
+
*/
|
|
3537
|
+
onFileTypesChange(listener: (types: string[]) => void): Disposable;
|
|
3453
3538
|
}
|
|
3454
3539
|
```
|
|
3455
3540
|
|
|
@@ -3555,8 +3640,6 @@ interface PackageCandidate {
|
|
|
3555
3640
|
readonly manifest: PackageManifest;
|
|
3556
3641
|
/** The checks the player ran. */
|
|
3557
3642
|
readonly checks: string[];
|
|
3558
|
-
/** Load it temporarily alongside the installed plugins. A reserved id (`vestra.*`) fails with `reserved`. */
|
|
3559
|
-
trial(): Promise<PackageTrial>;
|
|
3560
3643
|
/**
|
|
3561
3644
|
* Install it. The same source upgrades the installed plugin (its data stays); another source fails with `conflict` unless `replace` is set; a built-in id fails with `unsupported` (built-ins update with the player). A reserved id (`vestra` or `vestra.*`, which every built-in uses) fails with `reserved` (player 0.4.1; already when validated), as only the player installs built-in plugins.
|
|
3562
3645
|
*/
|
|
@@ -3564,21 +3647,6 @@ interface PackageCandidate {
|
|
|
3564
3647
|
}
|
|
3565
3648
|
```
|
|
3566
3649
|
|
|
3567
|
-
### PackageTrial
|
|
3568
|
-
|
|
3569
|
-
A trial load of a package. Disposing it stops the trial and waits for cleanup.
|
|
3570
|
-
|
|
3571
|
-
```ts
|
|
3572
|
-
interface PackageTrial extends Disposable {
|
|
3573
|
-
/** The trial's status as the player reports it. */
|
|
3574
|
-
readonly status: string;
|
|
3575
|
-
/**
|
|
3576
|
-
* Stop and wait until the host confirmed cleanup. Rejects with code `cleanup` when the host stopped the trial without confirming cleanup; calling `stop()` again then asks the host again.
|
|
3577
|
-
*/
|
|
3578
|
-
stop(): Promise<void>;
|
|
3579
|
-
}
|
|
3580
|
-
```
|
|
3581
|
-
|
|
3582
3650
|
### PackageChangeStatus
|
|
3583
3651
|
|
|
3584
3652
|
The progress of an install, rollback or uninstall.
|
|
@@ -3617,8 +3685,8 @@ interface PluginInfo {
|
|
|
3617
3685
|
id: string;
|
|
3618
3686
|
/** Version, `null` when unknown. */
|
|
3619
3687
|
version: string | null;
|
|
3620
|
-
/** From the install library, a `--plugin` development load
|
|
3621
|
-
origin: "installed" | "development"
|
|
3688
|
+
/** From the install library, or a `--plugin` development load. */
|
|
3689
|
+
origin: "installed" | "development";
|
|
3622
3690
|
/** Run state. */
|
|
3623
3691
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
3624
3692
|
/** Why it failed (`state: "failed"`). */
|
|
@@ -3631,7 +3699,7 @@ interface PluginInfo {
|
|
|
3631
3699
|
previousVersion: string | null;
|
|
3632
3700
|
/** Shipped with the player: it can be disabled, not uninstalled or replaced. `false` from players before 0.2.0. */
|
|
3633
3701
|
builtin: boolean;
|
|
3634
|
-
/** Where an installed plugin came from; `null` for development loads
|
|
3702
|
+
/** Where an installed plugin came from; `null` for development loads. */
|
|
3635
3703
|
source: PackageSource | null;
|
|
3636
3704
|
/** Its data directory (`plugin.dataDirectory` of that plugin); `null` when the player has none. The path's layout is not part of the API. */
|
|
3637
3705
|
dataDirectory: string | null;
|
|
@@ -3639,6 +3707,28 @@ interface PluginInfo {
|
|
|
3639
3707
|
* What a running plugin was refused because another plugin holds it: a panel position (`slot-occupied`, with `slot`) or the next/previous sequence (`sequence-taken`). Neither fails activation; `error` has the same as text. Empty from players before 0.4.x (after 0.4.0). Always set by the SDK; optional so that code building `PluginInfo` values itself (tests) keeps compiling.
|
|
3640
3708
|
*/
|
|
3641
3709
|
warnings?: readonly PluginWarning[];
|
|
3710
|
+
/**
|
|
3711
|
+
* The manifest's display texts in the interface language, translated with that package's catalog; absent from players before 0.4.3 (read `manifest.json` there).
|
|
3712
|
+
*/
|
|
3713
|
+
display?: PluginDisplay;
|
|
3714
|
+
}
|
|
3715
|
+
```
|
|
3716
|
+
|
|
3717
|
+
### PluginDisplay
|
|
3718
|
+
|
|
3719
|
+
PluginInfo.display: a package's `name`, `punchline`,
|
|
3720
|
+
`description` and `tags` as the player shows them.
|
|
3721
|
+
|
|
3722
|
+
```ts
|
|
3723
|
+
interface PluginDisplay {
|
|
3724
|
+
/** `name`, translated; `null` when the manifest has none. */
|
|
3725
|
+
readonly name: string | null;
|
|
3726
|
+
/** `punchline`, translated; `null` when the manifest has none. */
|
|
3727
|
+
readonly punchline: string | null;
|
|
3728
|
+
/** `description`, translated; `null` when the manifest has none. */
|
|
3729
|
+
readonly description: string | null;
|
|
3730
|
+
/** `tags`, each translated. */
|
|
3731
|
+
readonly tags: readonly string[];
|
|
3642
3732
|
}
|
|
3643
3733
|
```
|
|
3644
3734
|
|
|
@@ -3667,7 +3757,13 @@ need no capability; everything else needs `plugins.manage`.
|
|
|
3667
3757
|
```ts
|
|
3668
3758
|
interface Packages {
|
|
3669
3759
|
/**
|
|
3670
|
-
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
3760
|
+
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
3761
|
+
* reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
|
|
3762
|
+
* `reserved` (player 0.4.1).
|
|
3763
|
+
*
|
|
3764
|
+
* The player keeps the 32 most recent candidates of all plugins; an
|
|
3765
|
+
* older one's `install` fails with `stale` (validate again). Before
|
|
3766
|
+
* player 0.4.2 the 33rd validation of a run failed with `busy` instead.
|
|
3671
3767
|
*/
|
|
3672
3768
|
validate(directory: string): Promise<PackageCandidate>;
|
|
3673
3769
|
/**
|
|
@@ -3681,11 +3777,11 @@ interface Packages {
|
|
|
3681
3777
|
/** The new list after any start, stop, failure, install or uninstall. No capability. */
|
|
3682
3778
|
onChange(listener: (plugins: PluginInfo[]) => void): Disposable;
|
|
3683
3779
|
/**
|
|
3684
|
-
* 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, `
|
|
3780
|
+
* 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.
|
|
3685
3781
|
*/
|
|
3686
3782
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
3687
3783
|
/**
|
|
3688
|
-
* 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
|
|
3784
|
+
* 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.
|
|
3689
3785
|
*/
|
|
3690
3786
|
uninstall(id: string, options?: {
|
|
3691
3787
|
removeData?: boolean;
|
|
@@ -4243,6 +4339,15 @@ TestHost.coreAction (the player's core actions).
|
|
|
4243
4339
|
interface FakeCommand {
|
|
4244
4340
|
/** What the plugin declared: id, menu path, title, order, group, default bindings, focus and repeat. Wire-level shape; it may gain fields in minor versions. */
|
|
4245
4341
|
declaration: CommandDeclaration;
|
|
4342
|
+
/**
|
|
4343
|
+
* What the menu shows, as the player shows it in the test host's TestHost.locale: the title and each path segment translated with the plugin's catalog (the player's own menus in the player's words), the keys normalized (`@video` is "Video").
|
|
4344
|
+
*/
|
|
4345
|
+
readonly display: {
|
|
4346
|
+
/** The menu text of the item. */
|
|
4347
|
+
title: string;
|
|
4348
|
+
/** Each segment of the path as shown; empty without a path. */
|
|
4349
|
+
path: string[];
|
|
4350
|
+
};
|
|
4246
4351
|
/** The command state the player last received (after the update pass). */
|
|
4247
4352
|
state: {
|
|
4248
4353
|
/** `false` when the plugin disabled the command. */
|
|
@@ -4380,10 +4485,62 @@ class TestHost implements Transport {
|
|
|
4380
4485
|
*/
|
|
4381
4486
|
sidebar: LayoutSlot | null;
|
|
4382
4487
|
};
|
|
4383
|
-
/**
|
|
4488
|
+
/**
|
|
4489
|
+
* Every `osd.show` text as the player shows it (translated with the plugin's catalog and formatted with its args), newest last. The template and args the plugin sent are in `host.sent("osd.show")`.
|
|
4490
|
+
*/
|
|
4384
4491
|
readonly osd: string[];
|
|
4385
|
-
/** The text of this plugin's osd panel, `null` when hidden. */
|
|
4492
|
+
/** The text of this plugin's osd panel as shown, `null` when hidden. */
|
|
4386
4493
|
osdPanel: string | null;
|
|
4494
|
+
/**
|
|
4495
|
+
* The interface language the test host plays (`plugin.i18n.locale`), `"en"`
|
|
4496
|
+
* by default. Set it before startPlugin (or pass its `locale`
|
|
4497
|
+
* option): the plugin's catalog for it is read from the package's
|
|
4498
|
+
* `locales/` then, and the menus (FakeCommand.display), OSD
|
|
4499
|
+
* messages, dialog texts, settings page and panel titles show its
|
|
4500
|
+
* translations, the way the player shows them.
|
|
4501
|
+
*
|
|
4502
|
+
* The player's own texts (core actions' messages and menus, its
|
|
4503
|
+
* refusals) are English, or the player's Simplified Chinese for
|
|
4504
|
+
* `"zh-Hans"`. While `locale` was never set they stay in Chinese, as
|
|
4505
|
+
* players up to 0.4.2 showed them, so tests written for SDK 1.5.8 keep
|
|
4506
|
+
* passing; set `locale` to test what a user sees now.
|
|
4507
|
+
*/
|
|
4508
|
+
get locale(): Locale;
|
|
4509
|
+
set locale(locale: Locale);
|
|
4510
|
+
/**
|
|
4511
|
+
* Read the plugin's translations for TestHost.locale from a package directory, as the player would (`locales/<locale>.json` of a built package, else the source `locales/<locale>.po`); `startPlugin` does this. None for English; a missing file means none; a broken `.po` throws.
|
|
4512
|
+
* @param directory - The package directory (holding `locales/`).
|
|
4513
|
+
*/
|
|
4514
|
+
loadCatalog(directory: string): void;
|
|
4515
|
+
/**
|
|
4516
|
+
* A text of the plugin as the player shows it: translated with the plugin's catalog (TestHost.loadCatalog), then formatted with `args` when given.
|
|
4517
|
+
* @param text - English source text or template.
|
|
4518
|
+
* @param args - Values for its placeholders.
|
|
4519
|
+
* @returns The shown text.
|
|
4520
|
+
*/
|
|
4521
|
+
translate(text: string, args?: TemplateArgs): string;
|
|
4522
|
+
/** The saved interface language (`plugin.appearance.language`), changed by `appearance.setLanguage` or TestHost.setLanguage. */
|
|
4523
|
+
language: LanguagePreference;
|
|
4524
|
+
/**
|
|
4525
|
+
* Simulate another plugin (or the user) saving the interface language: pushes `appearance.changed`.
|
|
4526
|
+
* @param language - `"system"` or a locale.
|
|
4527
|
+
*/
|
|
4528
|
+
setLanguage(language: LanguagePreference): void;
|
|
4529
|
+
/**
|
|
4530
|
+
* Every `dialogs.confirm` as the player shows it: its texts translated (message and detail formatted with `args`), newest last. The options the plugin sent are in TestHost.confirms.
|
|
4531
|
+
*/
|
|
4532
|
+
readonly confirmTexts: {
|
|
4533
|
+
/** The question. */
|
|
4534
|
+
message: string;
|
|
4535
|
+
/** The text under it, if any. */
|
|
4536
|
+
detail?: string;
|
|
4537
|
+
/** The confirm button. */
|
|
4538
|
+
confirm: string;
|
|
4539
|
+
/** The cancel button (the player's "Cancel" by default). */
|
|
4540
|
+
cancel: string;
|
|
4541
|
+
/** The checkbox, if any. */
|
|
4542
|
+
checkbox?: string;
|
|
4543
|
+
}[];
|
|
4387
4544
|
/** The sequence provider's flags, `null` without a provider. */
|
|
4388
4545
|
sequence: {
|
|
4389
4546
|
/** The provider's `canNext`, as last evaluated. */
|
|
@@ -4721,6 +4878,12 @@ class TestHost implements Transport {
|
|
|
4721
4878
|
/** Exact host names it resolves (a web resolver), with `schemes`. */
|
|
4722
4879
|
hosts?: string[];
|
|
4723
4880
|
}[];
|
|
4881
|
+
/** What `plugin.resolvers.fileTypes` reports: the extensions of this plugin's file resolvers and of TestHost.otherResolvers, sorted, each once. */
|
|
4882
|
+
fileTypes(): string[];
|
|
4883
|
+
/**
|
|
4884
|
+
* Push `resolvers.fileTypes` when TestHost.fileTypes changed: called after this plugin's resolvers come and go; call it yourself after changing TestHost.otherResolvers (another plugin started or stopped).
|
|
4885
|
+
*/
|
|
4886
|
+
updateFileTypes(): void;
|
|
4724
4887
|
/**
|
|
4725
4888
|
* File opens waiting for this plugin's file resolver (`extensions`), by request id: the path and how it was opened. Opening a matching path (`player.open`, TestHost.openElsewhere) sends `source.resolveRequest`; the answer selects the stream, fails the open or, once the source changed, is `stale`. Any later source change cancels it (`source.resolveCancel`). Web resolvers are not simulated: emit `source.resolveRequest` yourself.
|
|
4726
4889
|
*/
|
|
@@ -4905,7 +5068,7 @@ class TestHost implements Transport {
|
|
|
4905
5068
|
/** Its version, `null` when unknown. */
|
|
4906
5069
|
version: string | null;
|
|
4907
5070
|
/** Where it was loaded from. */
|
|
4908
|
-
origin: "installed" | "development"
|
|
5071
|
+
origin: "installed" | "development";
|
|
4909
5072
|
/** Its run state. */
|
|
4910
5073
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
4911
5074
|
/** Why it failed (`state: "failed"`). */
|
|
@@ -5035,6 +5198,10 @@ function startPlugin(activate: (plugin: Plugin) => unknown, options?: {
|
|
|
5035
5198
|
host?: TestHost;
|
|
5036
5199
|
/** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
|
|
5037
5200
|
dataDirectory?: string;
|
|
5201
|
+
/**
|
|
5202
|
+
* The interface language to play (TestHost.locale): the plugin's `locales/<locale>.po` (or `.json`) in `directory` is loaded and its menus, messages and titles show translated. Default: the host's.
|
|
5203
|
+
*/
|
|
5204
|
+
locale?: Locale;
|
|
5038
5205
|
}): Promise<{
|
|
5039
5206
|
/** The test host the plugin talks to. */
|
|
5040
5207
|
host: TestHost;
|
|
@@ -5059,7 +5226,7 @@ own sensible display cap well below `perPlugin`.
|
|
|
5059
5226
|
|
|
5060
5227
|
```ts
|
|
5061
5228
|
const COMMAND_LIMITS: {
|
|
5062
|
-
/** Commands one plugin instance may have at the same time (160).
|
|
5229
|
+
/** Commands one plugin instance may have at the same time (160). */
|
|
5063
5230
|
readonly perPlugin: 160;
|
|
5064
5231
|
/** Commands of every plugin plus the player's own, together (1024). */
|
|
5065
5232
|
readonly total: 1024;
|
|
@@ -5080,6 +5247,16 @@ agrees with the player's.
|
|
|
5080
5247
|
const MENU_IDS: readonly ["open", "playback", "subtitles", "video", "audio", "screen", "settings"];
|
|
5081
5248
|
```
|
|
5082
5249
|
|
|
5250
|
+
### LOCALES
|
|
5251
|
+
|
|
5252
|
+
The player's interface languages: English (the source language) first,
|
|
5253
|
+
then the translations, as the player's own table (its tests check that
|
|
5254
|
+
they agree).
|
|
5255
|
+
|
|
5256
|
+
```ts
|
|
5257
|
+
const LOCALES: readonly ["en", "zh-Hans", "ja", "ko", "de", "fr", "es"];
|
|
5258
|
+
```
|
|
5259
|
+
|
|
5083
5260
|
### HostError
|
|
5084
5261
|
|
|
5085
5262
|
A definite refusal from the player (or the plugin runner), with a stable
|
|
@@ -5214,6 +5391,8 @@ interface Transport {
|
|
|
5214
5391
|
on(event: string, listener: (data: unknown) => void): () => void;
|
|
5215
5392
|
/** Replaces the real local services (test host). */
|
|
5216
5393
|
local?: LocalServices;
|
|
5394
|
+
/** The interface language of this run (`plugin.i18n.locale`): the runner passes the player's `VESTRA_LOCALE`, the test host its `locale`. */
|
|
5395
|
+
locale?: string;
|
|
5217
5396
|
}
|
|
5218
5397
|
```
|
|
5219
5398
|
|
|
@@ -7117,3 +7296,171 @@ test("the window shows sample.md, with the package as its base", async () => {
|
|
|
7117
7296
|
await stop();
|
|
7118
7297
|
});
|
|
7119
7298
|
```
|
|
7299
|
+
|
|
7300
|
+
### Example: i18n
|
|
7301
|
+
|
|
7302
|
+
A plugin in the player's language: **Tools → Counter…** opens a window with one button that counts clicks. Every text is English in the source; `locales/zh-Hans.po` translates it into Simplified Chinese, and the player shows the translation when its interface language is Simplified Chinese.
|
|
7303
|
+
|
|
7304
|
+
| Capabilities | API |
|
|
7305
|
+
| --- | --- |
|
|
7306
|
+
| `commands`, `ui.window`, `ui.osd` | `commands.add`, `windows.create`, `osd.show(template, args)`; Slint `@tr`; `vestra-plugin i18n extract` and `check`; tests with `startPlugin({ locale })` |
|
|
7307
|
+
|
|
7308
|
+
#### Walkthrough
|
|
7309
|
+
|
|
7310
|
+
A plugin in the player's language. **Tools ▸ Counter…** opens a window with one button; each click counts and shows the count on the video. All texts are English in the source, and `locales/zh-Hans.po` translates them: with the player in Simplified Chinese the menu reads **工具 ▸ 计数器…** and the window **2 次点击**. The plugin contains no translation code.
|
|
7311
|
+
|
|
7312
|
+
#### The code
|
|
7313
|
+
|
|
7314
|
+
(code: `main.ts`, below)
|
|
7315
|
+
|
|
7316
|
+
- The menu path is English. The player shows each segment translated with the plugin's catalog; menus of all plugins merge by these English words.
|
|
7317
|
+
- The window gets data (`count`), not text.
|
|
7318
|
+
- plugin.osd.show takes a template and its arguments. The player translates `"Count: {}"`, then puts `count` into `{}`. A sentence built in TypeScript (`` `Count: ${count}` ``) could not be translated.
|
|
7319
|
+
|
|
7320
|
+
#### The window
|
|
7321
|
+
|
|
7322
|
+
(code: `ui/counter.slint`, below)
|
|
7323
|
+
|
|
7324
|
+
Every text is in `@tr("…")`. The player replaces the English literal with the translation when it loads the file; Slint then fills in the arguments. `"{n} click" | "{n} clicks" % root.count` has two forms: the first for 1, the second for every other number (`{n}` is the number).
|
|
7325
|
+
|
|
7326
|
+
#### The translation
|
|
7327
|
+
|
|
7328
|
+
`vestra-plugin i18n extract` collected the texts into `locales/messages.pot`; `extract --locale zh-Hans` started `locales/zh-Hans.po`, and each `msgstr` was then filled in:
|
|
7329
|
+
|
|
7330
|
+
(code: `locales/zh-Hans.po`, below)
|
|
7331
|
+
|
|
7332
|
+
Chinese has one plural form, so `msgstr[0]` is enough. `vestra-plugin i18n check` reports what is missing; `vestra-plugin build` compiles the file into `dist/locales/zh-Hans.json`, which the player reads.
|
|
7333
|
+
|
|
7334
|
+
#### The test
|
|
7335
|
+
|
|
7336
|
+
(code: `tests/main.test.ts`, below)
|
|
7337
|
+
|
|
7338
|
+
`startPlugin(activate, { directory, locale })` loads the catalog of that language. `host.command(id).display` is the menu as the user sees it, `declaration` what the code declared; `host.osd` lists the messages as shown.
|
|
7339
|
+
|
|
7340
|
+
#### examples/i18n/manifest.json
|
|
7341
|
+
|
|
7342
|
+
```json
|
|
7343
|
+
{
|
|
7344
|
+
"id": "examples.i18n",
|
|
7345
|
+
"version": "0.1.0",
|
|
7346
|
+
"apiVersion": "vestra/1",
|
|
7347
|
+
"entry": "main.ts",
|
|
7348
|
+
"minPlayerVersion": "0.4.3",
|
|
7349
|
+
"capabilities": ["commands", "ui.window", "ui.osd"],
|
|
7350
|
+
"name": "Counter",
|
|
7351
|
+
"author": "Vestra Player SDK examples",
|
|
7352
|
+
"punchline": "A click counter in the player's language",
|
|
7353
|
+
"description": "Tools ▸ Counter… opens a window with one button that counts clicks.\nSDK example: a plugin translated into Simplified Chinese.",
|
|
7354
|
+
"tags": ["example"]
|
|
7355
|
+
}
|
|
7356
|
+
```
|
|
7357
|
+
|
|
7358
|
+
#### examples/i18n/main.ts
|
|
7359
|
+
|
|
7360
|
+
```ts
|
|
7361
|
+
/**
|
|
7362
|
+
* Counter: Tools ▸ Counter… opens a window with one button that counts
|
|
7363
|
+
* clicks, in the player's language. The texts are English; the player
|
|
7364
|
+
* translates them with locales/<lang>.po. Walkthrough: docs/examples/i18n.md.
|
|
7365
|
+
*/
|
|
7366
|
+
import type { Plugin } from "@vestra-player/sdk";
|
|
7367
|
+
|
|
7368
|
+
export default function activate(plugin: Plugin) {
|
|
7369
|
+
let count = 0;
|
|
7370
|
+
|
|
7371
|
+
// The window's texts are in its Slint file (@tr); TypeScript passes data.
|
|
7372
|
+
const window = plugin.windows.create("ui/counter.slint");
|
|
7373
|
+
window.bind(() => ({ count }));
|
|
7374
|
+
window.on("add", () => {
|
|
7375
|
+
count += 1;
|
|
7376
|
+
// A template and its arguments, never a sentence built in TypeScript.
|
|
7377
|
+
return plugin.osd.show("Count: {}", [count]);
|
|
7378
|
+
});
|
|
7379
|
+
|
|
7380
|
+
// Declared texts are English; the player shows them translated.
|
|
7381
|
+
plugin.commands.add({
|
|
7382
|
+
id: "counter",
|
|
7383
|
+
path: ["Tools", "Counter…"],
|
|
7384
|
+
run: () => window.show(),
|
|
7385
|
+
});
|
|
7386
|
+
}
|
|
7387
|
+
```
|
|
7388
|
+
|
|
7389
|
+
#### examples/i18n/ui/counter.slint
|
|
7390
|
+
|
|
7391
|
+
```slint
|
|
7392
|
+
// The Counter window. Every text is @tr("English"): the player puts the
|
|
7393
|
+
// translation in its place when it loads the file. Walkthrough:
|
|
7394
|
+
// docs/examples/i18n.md.
|
|
7395
|
+
import { Theme } from "@vestra/theme.slint";
|
|
7396
|
+
import { Button } from "@vestra/widgets.slint";
|
|
7397
|
+
|
|
7398
|
+
export component CounterWindow inherits Window {
|
|
7399
|
+
title: @tr("Counter");
|
|
7400
|
+
width: 240px;
|
|
7401
|
+
background: Theme.bg-panel;
|
|
7402
|
+
|
|
7403
|
+
in property <int> count;
|
|
7404
|
+
callback add();
|
|
7405
|
+
|
|
7406
|
+
VerticalLayout {
|
|
7407
|
+
padding: Theme.dialog-padding;
|
|
7408
|
+
spacing: 8px;
|
|
7409
|
+
Text {
|
|
7410
|
+
// One form for 1, the other for every other number.
|
|
7411
|
+
text: @tr("{n} click" | "{n} clicks" % root.count);
|
|
7412
|
+
color: Theme.text;
|
|
7413
|
+
font-size: Theme.font-body;
|
|
7414
|
+
}
|
|
7415
|
+
Button {
|
|
7416
|
+
text: @tr("Add one");
|
|
7417
|
+
clicked => { root.add(); }
|
|
7418
|
+
}
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
```
|
|
7422
|
+
|
|
7423
|
+
#### examples/i18n/tests/main.test.ts
|
|
7424
|
+
|
|
7425
|
+
```ts
|
|
7426
|
+
import assert from "node:assert/strict";
|
|
7427
|
+
import test from "node:test";
|
|
7428
|
+
import { startPlugin } from "@vestra-player/sdk/testing";
|
|
7429
|
+
import activate from "../main.ts";
|
|
7430
|
+
|
|
7431
|
+
const directory = new URL("..", import.meta.url); // manifest.json and locales/
|
|
7432
|
+
|
|
7433
|
+
test("English: the texts as written", async () => {
|
|
7434
|
+
const { host, stop } = await startPlugin(activate, {
|
|
7435
|
+
directory,
|
|
7436
|
+
locale: "en",
|
|
7437
|
+
});
|
|
7438
|
+
const counter = host.command("counter");
|
|
7439
|
+
assert.deepEqual(counter.display.path, ["Tools", "Counter…"]);
|
|
7440
|
+
await counter.run();
|
|
7441
|
+
host.window("counter").emit("add");
|
|
7442
|
+
await host.settle();
|
|
7443
|
+
assert.deepEqual(host.osd, ["Count: 1"]);
|
|
7444
|
+
await stop();
|
|
7445
|
+
});
|
|
7446
|
+
|
|
7447
|
+
test("Simplified Chinese: the menu and the message from locales/zh-Hans.po", async () => {
|
|
7448
|
+
const { host, plugin, stop } = await startPlugin(activate, {
|
|
7449
|
+
directory,
|
|
7450
|
+
locale: "zh-Hans",
|
|
7451
|
+
});
|
|
7452
|
+
assert.equal(plugin.i18n.locale, "zh-Hans");
|
|
7453
|
+
const counter = host.command("counter");
|
|
7454
|
+
// The key stays English; the menu shows the translation.
|
|
7455
|
+
assert.deepEqual(counter.declaration.path, ["Tools", "Counter…"]);
|
|
7456
|
+
assert.deepEqual(counter.display.path, ["工具", "计数器…"]);
|
|
7457
|
+
await counter.run();
|
|
7458
|
+
const window = host.window("counter");
|
|
7459
|
+
window.emit("add");
|
|
7460
|
+
window.emit("add");
|
|
7461
|
+
await host.settle();
|
|
7462
|
+
assert.equal(window.props["count"], 2);
|
|
7463
|
+
assert.deepEqual(host.osd, ["计数:1", "计数:2"]);
|
|
7464
|
+
await stop();
|
|
7465
|
+
});
|
|
7466
|
+
```
|