@vestra-player/sdk 1.5.34 → 1.5.36
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 +37 -19
- package/dist/api-requests.js +3 -0
- package/dist/capabilities.d.ts +2 -2
- package/dist/capabilities.js +2 -2
- package/dist/commands.d.ts +4 -1
- package/dist/commands.js +5 -1
- package/dist/host-capabilities.js +12 -0
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -1
- package/dist/plugin/services.d.ts +2 -1
- package/dist/plugin/services.js +22 -2
- package/dist/plugin/testing.d.ts +75 -5
- package/dist/plugin/testing.js +137 -4
- package/dist/plugin/types.d.ts +353 -0
- package/dist/wire.d.ts +29 -0
- package/dist/wire.js +11 -0
- package/llms-full.txt +519 -10
- package/llms.txt +22 -21
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.36: 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
|
|
|
@@ -1613,6 +1613,127 @@ export default async function activate(plugin: Plugin) {
|
|
|
1613
1613
|
- **Preferences rather than state:** a choice such as "reopen my windows at startup" belongs on a settings page (plugin.settings.page) that reads and writes the same store (the `SettingsPage` reference has an example).
|
|
1614
1614
|
- **Large or binary data:** write your own files in plugin.dataDirectory (to a temporary name, then rename) and keep only small settings in `storage.json`.
|
|
1615
1615
|
|
|
1616
|
+
## How-to: Offer a GPU rendering switch
|
|
1617
|
+
|
|
1618
|
+
**Goal:** a settings menu item "GPU Rendering (Experimental)" that shows whether the player will draw on the GPU, is greyed out with the player's reason where the GPU cannot be chosen, saves the user's choice, says "(Restart to Apply)" while the choice waits for a restart, and offers that restart.
|
|
1619
|
+
|
|
1620
|
+
The player keeps the choice itself, like a browser's hardware acceleration switch: setPreference saves it in the player's settings at once, the running player keeps its renderer, and the next start uses the choice **whether or not the user restarts now**. Your plugin stores nothing. Reading the state needs no capability; saving a choice and restarting need `rendering.manage`, which needs player 0.5.13.
|
|
1621
|
+
|
|
1622
|
+
#### The complete example
|
|
1623
|
+
|
|
1624
|
+
`manifest.json`:
|
|
1625
|
+
|
|
1626
|
+
```json
|
|
1627
|
+
{
|
|
1628
|
+
"id": "examples.gpu-switch",
|
|
1629
|
+
"version": "1.0.0",
|
|
1630
|
+
"apiVersion": "vestra/1",
|
|
1631
|
+
"entry": "main.ts",
|
|
1632
|
+
"minPlayerVersion": "0.5.13",
|
|
1633
|
+
"capabilities": ["commands", "rendering.manage"]
|
|
1634
|
+
}
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
`main.ts`:
|
|
1638
|
+
|
|
1639
|
+
```ts
|
|
1640
|
+
import type { Plugin, PluginError, RenderingState, Scope } from "@vestra-player/sdk";
|
|
1641
|
+
|
|
1642
|
+
const TITLE = "GPU Rendering (Experimental)";
|
|
1643
|
+
|
|
1644
|
+
export default async function activate(plugin: Plugin) {
|
|
1645
|
+
let state: RenderingState = await plugin.rendering.state();
|
|
1646
|
+
let menu: Scope | null = null;
|
|
1647
|
+
let shownPending: boolean | null = null;
|
|
1648
|
+
|
|
1649
|
+
// A command's title cannot change in place, so the items live in a child
|
|
1650
|
+
// scope that is rebuilt when `restartPending` flips.
|
|
1651
|
+
async function build() {
|
|
1652
|
+
if (state.restartPending === shownPending) return;
|
|
1653
|
+
shownPending = state.restartPending;
|
|
1654
|
+
await menu?.dispose();
|
|
1655
|
+
menu = plugin.scope();
|
|
1656
|
+
menu.commands.add({
|
|
1657
|
+
id: "gpu-rendering",
|
|
1658
|
+
path: ["@settings", state.restartPending ? `${TITLE} (Restart to Apply)` : TITLE],
|
|
1659
|
+
// Checked when the next start draws on the GPU (also for "auto").
|
|
1660
|
+
checked: () => state.nextMode === "gpu",
|
|
1661
|
+
// Greyed out with the player's reason where "gpu" cannot be chosen.
|
|
1662
|
+
enabled: () => state.gpu.available,
|
|
1663
|
+
reason: () => state.gpu.reason?.message ?? null,
|
|
1664
|
+
run: async () => {
|
|
1665
|
+
const choice = state.nextMode === "gpu" ? "software" : "gpu";
|
|
1666
|
+
try {
|
|
1667
|
+
state = await plugin.rendering.setPreference(choice);
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
// `unavailable`: the GPU was ruled out after the state was read.
|
|
1670
|
+
state = await plugin.rendering.state(); // the item now says why
|
|
1671
|
+
if ((error as PluginError).code !== "unavailable") throw error; // `failed`: not saved
|
|
1672
|
+
} finally {
|
|
1673
|
+
await build();
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
});
|
|
1677
|
+
if (state.restartPending)
|
|
1678
|
+
menu.commands.add({
|
|
1679
|
+
id: "restart",
|
|
1680
|
+
path: ["@settings", "Restart Player to Apply"],
|
|
1681
|
+
run: async () => {
|
|
1682
|
+
// Resolves once the player accepted; it then quits and starts again.
|
|
1683
|
+
await plugin.rendering.restartPlayer();
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
await build();
|
|
1689
|
+
}
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
#### How it works
|
|
1693
|
+
|
|
1694
|
+
- **`state()` describes now and the next start.** `mode` and `reason` are the renderer drawing now and why; `preference` is the saved choice (`"auto"`, `"gpu"` or `"software"`); `nextMode` and `nextReason` are what the next start will use as far as the player knows, after the environment, the driver denylist and an earlier GPU crash. The item is checked by `nextMode`, so with `"auto"` on a system where the GPU is the default it shows checked.
|
|
1695
|
+
- **`gpu.available` decides whether the item can be used.** Where this player has no GPU renderer (`no-gpu-backend`, Windows today), without a user interface (`headless`), for a graphics driver on the player's denylist (`denylist`) or after a GPU crash at an earlier start (`previous-crash`), it is `false` and `gpu.reason.message` explains why; the item shows that as its disabled reason. `"software"` and `"auto"` are always accepted, so a user can always leave the GPU.
|
|
1696
|
+
- **`restartPending`** is true while the next start's mode differs from the mode this run started with. When the user changes the choice back, it turns false again and the "(Restart to Apply)" title and the restart item go away.
|
|
1697
|
+
- **`restartPlayer()`** resolves once the player has accepted. The player then quits normally, which stops every plugin, yours included, and starts again with the same plugins; a local file continues at its position, paused or playing as it was (a stream is not reopened). It rejects with `busy` while a restart is under way and with `failed` when the new process could not start (the player keeps running).
|
|
1698
|
+
|
|
1699
|
+
The restart is only a shortcut. A user who ignores it gets the new renderer the next time they start the player.
|
|
1700
|
+
|
|
1701
|
+
#### Older players
|
|
1702
|
+
|
|
1703
|
+
A player before 0.5.13 does not load a plugin that declares `rendering.manage`, so the manifest above sets `minPlayerVersion`. A plugin that only reads the state, without the capability, gets `unsupported` from `state()` on an older player:
|
|
1704
|
+
|
|
1705
|
+
```ts
|
|
1706
|
+
const state = await plugin.rendering.state().catch((error: PluginError) => {
|
|
1707
|
+
if (error.code === "unsupported") return null; // a player before 0.5.13
|
|
1708
|
+
throw error;
|
|
1709
|
+
});
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
To know only whether render options work now, read rendering from `plugin.app.system()` instead; `renderingReason` there is the same reason as `state().reason`.
|
|
1713
|
+
|
|
1714
|
+
#### Test it
|
|
1715
|
+
|
|
1716
|
+
TestHost.rendering models the player: preset `gpu`, `reason` and `startedWith` before the plugin starts (`videoRender.gpu` is the mode drawing now), then drive the item and check the saved choice.
|
|
1717
|
+
|
|
1718
|
+
```ts
|
|
1719
|
+
import assert from "node:assert/strict";
|
|
1720
|
+
import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
|
|
1721
|
+
import activate from "../main.ts";
|
|
1722
|
+
|
|
1723
|
+
const host = new TestHost(); // a GPU player, "auto", started on the GPU
|
|
1724
|
+
const { stop } = await startPlugin(activate, {
|
|
1725
|
+
directory: new URL("..", import.meta.url),
|
|
1726
|
+
host,
|
|
1727
|
+
});
|
|
1728
|
+
await host.command("gpu-rendering").run(); // unchecks: saves "software"
|
|
1729
|
+
assert.equal(host.rendering.preference, "software");
|
|
1730
|
+
await host.command("restart").run(); // offered while the restart is pending
|
|
1731
|
+
assert.equal(host.rendering.restarts, 1);
|
|
1732
|
+
await stop();
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
Set `host.rendering.gpu = { available: false, reason: { code: "denylist", message: "…" } }` to see the item greyed out, and refuse a call with `host.handle("rendering.restart", () => { throw new HostError("failed", "…"); })`.
|
|
1736
|
+
|
|
1616
1737
|
## How-to: Translate a plugin
|
|
1617
1738
|
|
|
1618
1739
|
Vestra Player (0.4.3 or later) shows its interface in one of seven languages. The user picks one in **Settings ▸ Language** (or follows the system); the choice takes effect at the next start, and the language stays the same for the whole run. A plugin joins in by shipping one gettext file per language. It writes **no translation code**: the player translates.
|
|
@@ -2044,7 +2165,7 @@ The test decides what the player is doing:
|
|
|
2044
2165
|
- `host.openElsewhere(path, phase?)`: the user or another plugin opened a source (a new source id, then `phase`, `"ready"` by default). Calls the plugin made against the old state now fail with `stale`.
|
|
2045
2166
|
- `host.setSource({ phase, position, duration, paused, audioTracks, … })`: the state changed (the user paused, playback reached the end). Before `startPlugin`, it presets the source the plugin finds at activation.
|
|
2046
2167
|
- `host.setMainWindow({ fullscreen, videoArea })`, `host.setAppearance(theme)`, `host.setPackages(list)`: the main window, the colour scheme, the installed plugins changed.
|
|
2047
|
-
- Preset data the player would return: `host.stats` (`player.stats()`), `host.chapters`, `host.clipboard.text`, `host.secrets`, `host.systemInfo
|
|
2168
|
+
- Preset data the player would return: `host.stats` (`player.stats()`), `host.chapters`, `host.clipboard.text`, `host.secrets`, `host.systemInfo`, `host.rendering` (the GPU or software choice, see Offer a GPU rendering switch); `host.tools.handle("ffprobe", (args) => ({ stdout }))` makes a tool available and answers its runs.
|
|
2048
2169
|
|
|
2049
2170
|
#### Inject failures
|
|
2050
2171
|
|
|
@@ -2568,6 +2689,7 @@ The table lists the API whose documentation names a player version, newest last:
|
|
|
2568
2689
|
| 0.5.5 | SessionStorage |
|
|
2569
2690
|
| 0.5.10 | CommandButton, CommandOptions.button |
|
|
2570
2691
|
| 0.5.12 | SystemInfo.rendering |
|
|
2692
|
+
| 0.5.13 | SystemInfo.renderingReason, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState, Rendering |
|
|
2571
2693
|
|
|
2572
2694
|
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).
|
|
2573
2695
|
|
|
@@ -2604,6 +2726,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
|
|
|
2604
2726
|
| `ui.settings` | Settings.page, Settings.show | — | Add pages to the Options window | |
|
|
2605
2727
|
| `ui.theme` | Appearance.set, Appearance.setLanguage | — | Change the player's palette | |
|
|
2606
2728
|
| `secrets` | Secrets.get, Secrets.set, Secrets.delete | — | Store sign-in details in the system keychain | yes |
|
|
2729
|
+
| `rendering.manage` | Rendering.setPreference, Rendering.restartPlayer | — | Choose GPU or software rendering and restart the player | yes |
|
|
2607
2730
|
|
|
2608
2731
|
**Also needs** applies to every member of the row: declare those capabilities as well. A few members or options need one more, documented at the member:
|
|
2609
2732
|
|
|
@@ -2615,7 +2738,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
|
|
|
2615
2738
|
|
|
2616
2739
|
#### Without a capability
|
|
2617
2740
|
|
|
2618
|
-
A member not in the table needs no capability: `plugin.tools`, `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui` (images and Markdown), `plugin.i18n`, reading window.state, appearance.theme
|
|
2741
|
+
A member not in the table needs no capability: `plugin.tools`, `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui` (images and Markdown), `plugin.i18n`, reading window.state, appearance.theme, packages.list and rendering.state. The table is `CAPABILITY_INFO`, which tests can import from `@vestra-player/sdk/testing` to check a manifest (CAPABILITY_INFO).
|
|
2619
2742
|
|
|
2620
2743
|
#### Catching a missing capability
|
|
2621
2744
|
|
|
@@ -3052,6 +3175,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
|
|
|
3052
3175
|
| Video ▸ Saturation +1% | `U` | `vestra.video/video.saturation.up` |
|
|
3053
3176
|
| Video ▸ Hue -1% | `I` | `vestra.video/video.hue.down` |
|
|
3054
3177
|
| Video ▸ Hue +1% | `O` | `vestra.video/video.hue.up` |
|
|
3178
|
+
| Video ▸ GPU Rendering (Experimental) | | `vestra.video/video.gpu-rendering` |
|
|
3055
3179
|
| Audio ▸ Volume + | `ArrowUp`, `Mouse:WheelUp` | `host/volume-up` |
|
|
3056
3180
|
| Audio ▸ Volume − | `ArrowDown`, `Mouse:WheelDown` | `host/volume-down` |
|
|
3057
3181
|
| Audio ▸ Mute | `M` | `host/mute` |
|
|
@@ -3264,6 +3388,7 @@ A plugin uses the same API on macOS and Windows; the player does the system-spec
|
|
|
3264
3388
|
| Snapshots and exports (MediaCapture) | Available. | Available; needs FFmpeg 6.1 or newer. |
|
|
3265
3389
|
| Generated streams (Player.openStream) | A Unix socket. | Loopback TCP with a token. |
|
|
3266
3390
|
| Rendering | On the GPU (OpenGL), with hardware decoding. | In software, with hardware decoding copied back (`d3d11va-copy` and similar). |
|
|
3391
|
+
| plugin.rendering (player 0.5.13) | `"gpu"` can be chosen unless the driver is ruled out (`denylist`, `previous-crash`). | `gpu.available` is `false` (`no-gpu-backend`); `setPreference("gpu")` fails with `unsupported`. |
|
|
3267
3392
|
| Render options (RenderOptions) | Available; `peakDetect` has no effect. | `unsupported` (software rendering). |
|
|
3268
3393
|
| Bindings | `Ctrl` also matches ⌘; `Cmd` is ⌘. | `Cmd` is the Windows logo key; `Cmd+Q` does not quit. |
|
|
3269
3394
|
| Keys taken first, standard close keys | See Keys the system takes first. | See Keys the system takes first. |
|
|
@@ -3802,6 +3927,8 @@ interface Scope extends Disposable {
|
|
|
3802
3927
|
readonly window: MainWindow;
|
|
3803
3928
|
/** The player's version and components, and the system it runs on. */
|
|
3804
3929
|
readonly app: App;
|
|
3930
|
+
/** GPU or software rendering: read the choice, change it (`rendering.manage`). */
|
|
3931
|
+
readonly rendering: Rendering;
|
|
3805
3932
|
/** Every command's bindings and the user's overrides (`bindings.manage`). */
|
|
3806
3933
|
readonly bindings: Bindings;
|
|
3807
3934
|
/** Runtime images for Slint `image` properties. */
|
|
@@ -7328,6 +7455,27 @@ interface SystemInfo {
|
|
|
7328
7455
|
* ```
|
|
7329
7456
|
*/
|
|
7330
7457
|
readonly rendering?: "gpu" | "software";
|
|
7458
|
+
/**
|
|
7459
|
+
* Why SystemInfo.rendering is what it is now: the same reason as
|
|
7460
|
+
* RenderingState.reason (`default`, `no-gpu-backend`,
|
|
7461
|
+
* `preference`, `denylist`, `window-fallback`…, see
|
|
7462
|
+
* RenderingReasonCode). Read at each call, like `rendering`.
|
|
7463
|
+
*
|
|
7464
|
+
* Absent (`undefined`) on players before 0.5.13.
|
|
7465
|
+
* @player 0.5.13; older players leave it out (declare `"minPlayerVersion": "0.5.13"` when the plugin relies on it).
|
|
7466
|
+
* @example Say why the video is drawn in software
|
|
7467
|
+
* An About page explains a software renderer with the player's own words,
|
|
7468
|
+
* for example that the graphics driver is on the player's denylist.
|
|
7469
|
+
* `system()` reports the renderer and its reason together, with no
|
|
7470
|
+
* capability; the message is English text that may name the adapter and
|
|
7471
|
+
* the driver version.
|
|
7472
|
+
* ```ts
|
|
7473
|
+
* const { rendering, renderingReason } = await plugin.app.system();
|
|
7474
|
+
* if (rendering === "software" && renderingReason)
|
|
7475
|
+
* console.log(`Rendering: software (${renderingReason.message})`);
|
|
7476
|
+
* ```
|
|
7477
|
+
*/
|
|
7478
|
+
readonly renderingReason?: RenderingReason;
|
|
7331
7479
|
}
|
|
7332
7480
|
```
|
|
7333
7481
|
|
|
@@ -11368,6 +11516,298 @@ if (token === null) {
|
|
|
11368
11516
|
}
|
|
11369
11517
|
```
|
|
11370
11518
|
|
|
11519
|
+
### RenderingMode
|
|
11520
|
+
|
|
11521
|
+
Which renderer draws the video: `"gpu"` (the GPU renderer, where
|
|
11522
|
+
render options apply) or `"software"` (they fail
|
|
11523
|
+
with `unsupported`). The same values as SystemInfo.rendering.
|
|
11524
|
+
|
|
11525
|
+
- @player 0.5.13
|
|
11526
|
+
|
|
11527
|
+
```ts
|
|
11528
|
+
type RenderingMode = "gpu" | "software";
|
|
11529
|
+
```
|
|
11530
|
+
|
|
11531
|
+
Example: Tell the user which renderer draws the video
|
|
11532
|
+
|
|
11533
|
+
The plugin shows "Rendering: GPU" or "Rendering: software" on the
|
|
11534
|
+
video. `state()` asks the player; `mode` is the renderer drawing now,
|
|
11535
|
+
which can differ from the saved choice until the next start.
|
|
11536
|
+
```ts
|
|
11537
|
+
const { mode } = await plugin.rendering.state();
|
|
11538
|
+
const label: Record<RenderingMode, string> = { gpu: "GPU", software: "software" };
|
|
11539
|
+
await plugin.osd.show("Rendering: {}", [label[mode]]);
|
|
11540
|
+
```
|
|
11541
|
+
|
|
11542
|
+
### RenderingPreference
|
|
11543
|
+
|
|
11544
|
+
The user's saved rendering choice: `"auto"` (the player decides: the GPU
|
|
11545
|
+
where this system has a GPU renderer that is not ruled out, software
|
|
11546
|
+
elsewhere), `"gpu"` or `"software"`. It applies from the next start.
|
|
11547
|
+
|
|
11548
|
+
- @player 0.5.13
|
|
11549
|
+
|
|
11550
|
+
```ts
|
|
11551
|
+
type RenderingPreference = "auto" | "gpu" | "software";
|
|
11552
|
+
```
|
|
11553
|
+
|
|
11554
|
+
Example: Describe the saved choice on a settings page
|
|
11555
|
+
|
|
11556
|
+
The plugin turns the saved choice into the words its settings page
|
|
11557
|
+
shows. `preference` is what the user chose, not what draws now:
|
|
11558
|
+
RenderingState.nextMode says what the next start will use.
|
|
11559
|
+
```ts
|
|
11560
|
+
const { preference } = await plugin.rendering.state();
|
|
11561
|
+
// One word per choice; the Record type makes sure none is missing.
|
|
11562
|
+
const words: Record<RenderingPreference, string> = {
|
|
11563
|
+
auto: "Automatic",
|
|
11564
|
+
gpu: "GPU",
|
|
11565
|
+
software: "Software",
|
|
11566
|
+
};
|
|
11567
|
+
console.log(words[preference]);
|
|
11568
|
+
```
|
|
11569
|
+
|
|
11570
|
+
### RenderingReasonCode
|
|
11571
|
+
|
|
11572
|
+
Why the player renders the way it does, as a stable kebab-case code. A
|
|
11573
|
+
newer player may add codes: compare with the ones you handle and show
|
|
11574
|
+
RenderingReason.message for the rest. The codes of player 0.5.13:
|
|
11575
|
+
|
|
11576
|
+
| Code | Meaning |
|
|
11577
|
+
| --- | --- |
|
|
11578
|
+
| `default` | Nothing ruled the GPU out; the platform's default. |
|
|
11579
|
+
| `pending` | GPU rendering is starting. |
|
|
11580
|
+
| `headless` | The player runs without a user interface. |
|
|
11581
|
+
| `no-gpu-backend` | This player has no GPU renderer on this system (Windows today). |
|
|
11582
|
+
| `env` | The environment chose it (`VESTRA_RENDER`, `VESTRA_SOFTWARE_RENDER`). |
|
|
11583
|
+
| `preference` | The user chose software rendering (Rendering.setPreference); GPU chosen and available reports `default`. |
|
|
11584
|
+
| `denylist` | The graphics driver is on the player's denylist. |
|
|
11585
|
+
| `previous-crash` | GPU rendering crashed at an earlier start; it is tried again after a driver or player update, or after 7 days. |
|
|
11586
|
+
| `init-failed` | GPU rendering could not start. |
|
|
11587
|
+
| `window-fallback` | A window could not show GPU frames; the player switched to software during the run. |
|
|
11588
|
+
|
|
11589
|
+
- @player 0.5.13
|
|
11590
|
+
|
|
11591
|
+
```ts
|
|
11592
|
+
type RenderingReasonCode = "default" | "pending" | "headless" | "no-gpu-backend" | "env" | "preference" | "denylist" | "previous-crash" | "init-failed" | "window-fallback" | (string & {});
|
|
11593
|
+
```
|
|
11594
|
+
|
|
11595
|
+
Example: Explain a driver problem differently from a user choice
|
|
11596
|
+
|
|
11597
|
+
The plugin adds advice only when the graphics driver is the cause.
|
|
11598
|
+
`reason.code` is looked up among the codes the plugin knows; an unknown
|
|
11599
|
+
code from a newer player falls through to the player's own message.
|
|
11600
|
+
```ts
|
|
11601
|
+
const { reason } = await plugin.rendering.state();
|
|
11602
|
+
// Advice for the codes this plugin knows; any other code shows the message.
|
|
11603
|
+
const advice: Partial<Record<RenderingReasonCode, string>> = {
|
|
11604
|
+
denylist: "Update the graphics driver to use the GPU.",
|
|
11605
|
+
"previous-crash": "The GPU renderer crashed earlier; it is retried after an update.",
|
|
11606
|
+
};
|
|
11607
|
+
console.log(advice[reason.code] ?? reason.message);
|
|
11608
|
+
```
|
|
11609
|
+
|
|
11610
|
+
### RenderingReason
|
|
11611
|
+
|
|
11612
|
+
Why the player renders, or will render, the way it does: a stable
|
|
11613
|
+
RenderingReasonCode to branch on and a sentence to show.
|
|
11614
|
+
|
|
11615
|
+
- @player 0.5.13
|
|
11616
|
+
|
|
11617
|
+
```ts
|
|
11618
|
+
interface RenderingReason {
|
|
11619
|
+
/** The reason as a stable kebab-case code; branch on this. */
|
|
11620
|
+
readonly code: RenderingReasonCode;
|
|
11621
|
+
/** The reason as English text for people, for example with the graphics adapter's name and driver version; never branch on it. */
|
|
11622
|
+
readonly message: string;
|
|
11623
|
+
}
|
|
11624
|
+
```
|
|
11625
|
+
|
|
11626
|
+
Example: Show why GPU rendering is off
|
|
11627
|
+
|
|
11628
|
+
The plugin logs the player's explanation when the video is drawn in
|
|
11629
|
+
software. `message` is the player's English text and may name the
|
|
11630
|
+
graphics adapter and driver version; `code` is for decisions.
|
|
11631
|
+
```ts
|
|
11632
|
+
const { mode, reason } = await plugin.rendering.state();
|
|
11633
|
+
if (mode === "software") console.log(`${reason.code}: ${reason.message}`);
|
|
11634
|
+
```
|
|
11635
|
+
|
|
11636
|
+
### RenderingState
|
|
11637
|
+
|
|
11638
|
+
What Rendering.state and Rendering.setPreference report:
|
|
11639
|
+
the renderer drawing now and why, the saved choice, what the next start
|
|
11640
|
+
will use, and whether `"gpu"` can be chosen.
|
|
11641
|
+
|
|
11642
|
+
- @player 0.5.13
|
|
11643
|
+
|
|
11644
|
+
```ts
|
|
11645
|
+
interface RenderingState {
|
|
11646
|
+
/** The renderer drawing the video now; the same as SystemInfo.rendering. */
|
|
11647
|
+
readonly mode: RenderingMode;
|
|
11648
|
+
/** Why RenderingState.mode is what it is now. */
|
|
11649
|
+
readonly reason: RenderingReason;
|
|
11650
|
+
/** The saved choice; it applies from the next start. */
|
|
11651
|
+
readonly preference: RenderingPreference;
|
|
11652
|
+
/** The renderer the next start will use, as far as the player knows now: from the saved choice, the environment, the driver denylist and an earlier GPU crash. */
|
|
11653
|
+
readonly nextMode: RenderingMode;
|
|
11654
|
+
/** Why the next start will use RenderingState.nextMode. */
|
|
11655
|
+
readonly nextReason: RenderingReason;
|
|
11656
|
+
/**
|
|
11657
|
+
* `true` while RenderingState.nextMode differs from the mode this run started with, so a restart would change the renderer; `false` again when the user changes the choice back.
|
|
11658
|
+
*/
|
|
11659
|
+
readonly restartPending: boolean;
|
|
11660
|
+
/** Whether the user can choose `"gpu"` now. */
|
|
11661
|
+
readonly gpu: {
|
|
11662
|
+
/** `true` when `setPreference("gpu")` would be accepted. */
|
|
11663
|
+
readonly available: boolean;
|
|
11664
|
+
/** Why `"gpu"` cannot be chosen (`no-gpu-backend`, `headless`, `denylist`, `previous-crash`…); `null` while it is available. */
|
|
11665
|
+
readonly reason: RenderingReason | null;
|
|
11666
|
+
};
|
|
11667
|
+
}
|
|
11668
|
+
```
|
|
11669
|
+
|
|
11670
|
+
Example: Label a menu item while a change waits for a restart
|
|
11671
|
+
|
|
11672
|
+
The plugin adds "(Restart to Apply)" to its GPU item after the user
|
|
11673
|
+
changed the choice. `restartPending` compares the mode the next start
|
|
11674
|
+
will use with the mode this run started with, so it turns false again
|
|
11675
|
+
when the user changes the choice back.
|
|
11676
|
+
```ts
|
|
11677
|
+
const state = await plugin.rendering.state();
|
|
11678
|
+
const title = state.restartPending
|
|
11679
|
+
? "GPU Rendering (Experimental) (Restart to Apply)"
|
|
11680
|
+
: "GPU Rendering (Experimental)";
|
|
11681
|
+
console.log(title, state.nextMode, state.nextReason.message);
|
|
11682
|
+
```
|
|
11683
|
+
|
|
11684
|
+
### Rendering
|
|
11685
|
+
|
|
11686
|
+
`plugin.rendering`: the player's rendering choice, GPU or software.
|
|
11687
|
+
Reading it needs no capability; changing the saved choice and restarting
|
|
11688
|
+
the player need `rendering.manage`.
|
|
11689
|
+
|
|
11690
|
+
The choice works like a browser's hardware acceleration switch: the
|
|
11691
|
+
player saves it in its own settings at once, and the running player
|
|
11692
|
+
keeps its renderer. The next start uses it, whether the user restarts
|
|
11693
|
+
now through Rendering.restartPlayer or quits and starts the
|
|
11694
|
+
player later. The player can still rule the GPU out at a start (an
|
|
11695
|
+
environment variable, a graphics driver on its denylist, a GPU crash at
|
|
11696
|
+
an earlier start); RenderingState.nextMode and
|
|
11697
|
+
RenderingState.nextReason say what the next start will do.
|
|
11698
|
+
|
|
11699
|
+
The renderer is a choice for the whole player and a restart stops every
|
|
11700
|
+
plugin, so change it only when the user asks. A plugin that only needs
|
|
11701
|
+
to know whether render options work reads SystemInfo.rendering.
|
|
11702
|
+
|
|
11703
|
+
Players before 0.5.13 do not know these requests: `state()` rejects with
|
|
11704
|
+
`unsupported`, and they do not load a plugin that declares
|
|
11705
|
+
`rendering.manage` (an unknown capability). Declare
|
|
11706
|
+
`"minPlayerVersion": "0.5.13"` when the plugin needs this.
|
|
11707
|
+
|
|
11708
|
+
- @capability None to read the state; `rendering.manage` to change the choice or restart the player.
|
|
11709
|
+
- @player 0.5.13; older players reject the requests as unknown (`unsupported`).
|
|
11710
|
+
|
|
11711
|
+
```ts
|
|
11712
|
+
interface Rendering {
|
|
11713
|
+
/**
|
|
11714
|
+
* Read the renderer drawing now, the saved choice, what the next start will use and whether `"gpu"` can be chosen. No capability.
|
|
11715
|
+
* @capability None.
|
|
11716
|
+
* @throws unsupported (rejects) A player before 0.5.13, which does not know the request.
|
|
11717
|
+
* @example Find out whether the GPU can be chosen
|
|
11718
|
+
* The plugin reads the state once while it starts and keeps it for its
|
|
11719
|
+
* menu. Each call asks the player; `gpu.available` is false, with a
|
|
11720
|
+
* reason, where this player has no GPU renderer, without a user
|
|
11721
|
+
* interface, or when the driver is ruled out. An older player rejects
|
|
11722
|
+
* with `unsupported`, and the plugin then offers nothing.
|
|
11723
|
+
* ```ts
|
|
11724
|
+
* const state = await plugin.rendering.state().catch((error: PluginError) => {
|
|
11725
|
+
* if (error.code === "unsupported") return null; // a player before 0.5.13
|
|
11726
|
+
* throw error;
|
|
11727
|
+
* });
|
|
11728
|
+
* if (state && !state.gpu.available) console.log(state.gpu.reason?.message);
|
|
11729
|
+
* ```
|
|
11730
|
+
*/
|
|
11731
|
+
state(): Promise<RenderingState>;
|
|
11732
|
+
/**
|
|
11733
|
+
* Save the user's rendering choice. The player writes it to its settings at once; the running player keeps its renderer, and the next start uses the choice whether or not the user restarts now. `"auto"` and `"software"` are always accepted.
|
|
11734
|
+
* @param preference - `"auto"`, `"gpu"` or `"software"`.
|
|
11735
|
+
* @returns The new state; RenderingState.restartPending tells whether a restart would change the renderer.
|
|
11736
|
+
* @capability rendering.manage
|
|
11737
|
+
* @throws invalid (rejects) `preference` is not `"auto"`, `"gpu"` or `"software"`.
|
|
11738
|
+
* @throws unsupported (rejects) `"gpu"` where this player has no GPU renderer on this system, or runs without a user interface; also any value on a player before 0.5.13.
|
|
11739
|
+
* @throws unavailable (rejects) `"gpu"` while it is ruled out: the graphics driver is on the player's denylist, or GPU rendering crashed at an earlier start. The message says which, with the adapter's name and the driver version.
|
|
11740
|
+
* @throws failed (rejects) The player could not write its settings file.
|
|
11741
|
+
* @throws capability (rejects) The manifest does not declare `rendering.manage`.
|
|
11742
|
+
* @example Switch back to the automatic choice
|
|
11743
|
+
* A Reset item returns the renderer to the player's own choice and tells
|
|
11744
|
+
* the user when it takes effect. `setPreference` saves the choice and
|
|
11745
|
+
* resolves with the new state; `restartPending` is true when the next
|
|
11746
|
+
* start will draw differently from this run. A refused save leaves the
|
|
11747
|
+
* old choice in place.
|
|
11748
|
+
* ```ts
|
|
11749
|
+
* try {
|
|
11750
|
+
* const state = await plugin.rendering.setPreference("auto");
|
|
11751
|
+
* if (state.restartPending) await plugin.osd.show("Applies after a restart");
|
|
11752
|
+
* } catch (error) {
|
|
11753
|
+
* // `failed`: the player's settings file could not be written.
|
|
11754
|
+
* await plugin.osd.show("Not saved ({})", [(error as PluginError).code]);
|
|
11755
|
+
* }
|
|
11756
|
+
* ```
|
|
11757
|
+
*/
|
|
11758
|
+
setPreference(preference: RenderingPreference): Promise<RenderingState>;
|
|
11759
|
+
/**
|
|
11760
|
+
* Restart the player so that a changed choice applies now. Resolves once the player has accepted; the player then quits normally, which stops every plugin (this one included), and starts again with the same plugins. A local file that was open continues at its position, paused or playing as it was; a stream or another source that is not a file is not reopened.
|
|
11761
|
+
* @capability rendering.manage
|
|
11762
|
+
* @throws unsupported (rejects) The player runs without a user interface; also a player before 0.5.13.
|
|
11763
|
+
* @throws busy (rejects) A restart is already under way, or this player was itself started by a restart less than 30 seconds ago (so a plugin that restarts on activation cannot make a loop).
|
|
11764
|
+
* @throws failed (rejects) The new player process could not be started; the player keeps running.
|
|
11765
|
+
* @throws capability (rejects) The manifest does not declare `rendering.manage`.
|
|
11766
|
+
* @example Restart after asking the user
|
|
11767
|
+
* After the user changed the renderer, the plugin offers to restart now.
|
|
11768
|
+
* `confirm` asks first, because a restart closes every window (it needs
|
|
11769
|
+
* `ui.dialog`); once `restartPlayer` resolves, the player is quitting and
|
|
11770
|
+
* will stop this plugin, so nothing else is started after it.
|
|
11771
|
+
* ```ts
|
|
11772
|
+
* const { confirmed } = await plugin.dialogs.confirm({
|
|
11773
|
+
* message: "Restart the player now?",
|
|
11774
|
+
* detail: "The new renderer is used from the restart on. The video continues where it is.",
|
|
11775
|
+
* confirm: "Restart",
|
|
11776
|
+
* });
|
|
11777
|
+
* if (confirmed)
|
|
11778
|
+
* await plugin.rendering.restartPlayer().catch((error: PluginError) => {
|
|
11779
|
+
* // `failed`: the new player could not start; this one keeps running.
|
|
11780
|
+
* void plugin.osd.show("Could not restart ({})", [error.code]);
|
|
11781
|
+
* });
|
|
11782
|
+
* ```
|
|
11783
|
+
*/
|
|
11784
|
+
restartPlayer(): Promise<void>;
|
|
11785
|
+
}
|
|
11786
|
+
```
|
|
11787
|
+
|
|
11788
|
+
Example: Offer GPU rendering as a menu item
|
|
11789
|
+
|
|
11790
|
+
Settings ▸ GPU Rendering (Experimental) carries a check mark while the
|
|
11791
|
+
next start will draw on the GPU, is greyed out with the player's reason
|
|
11792
|
+
where the GPU cannot be chosen, and saves the other choice when picked.
|
|
11793
|
+
`state()` reads the player's view once; `setPreference` saves the choice
|
|
11794
|
+
and resolves with the new state, which the item's bound values read
|
|
11795
|
+
again after the handler. The how-to guide adds the restart.
|
|
11796
|
+
```ts
|
|
11797
|
+
let state = await plugin.rendering.state();
|
|
11798
|
+
plugin.commands.add({
|
|
11799
|
+
id: "gpu-rendering",
|
|
11800
|
+
path: ["@settings", "GPU Rendering (Experimental)"],
|
|
11801
|
+
checked: () => state.nextMode === "gpu", // also for "auto" on a GPU system
|
|
11802
|
+
enabled: () => state.gpu.available,
|
|
11803
|
+
reason: () => state.gpu.reason?.message ?? null, // shown while disabled
|
|
11804
|
+
run: async () => {
|
|
11805
|
+
const choice = state.nextMode === "gpu" ? "software" : "gpu";
|
|
11806
|
+
state = await plugin.rendering.setPreference(choice);
|
|
11807
|
+
},
|
|
11808
|
+
});
|
|
11809
|
+
```
|
|
11810
|
+
|
|
11371
11811
|
### VideoNode
|
|
11372
11812
|
|
|
11373
11813
|
One node of a VideoFilter chain, by `kind`. Values outside the
|
|
@@ -12064,7 +12504,7 @@ Members fall into a few groups:
|
|
|
12064
12504
|
TestHost.app, TestHost.systemInfo,
|
|
12065
12505
|
TestHost.chapters, TestHost.audioDevices,
|
|
12066
12506
|
TestHost.secrets, TestHost.clipboard,
|
|
12067
|
-
TestHost.tools.
|
|
12507
|
+
TestHost.tools, TestHost.rendering.
|
|
12068
12508
|
- **Assert** on records: TestHost.osd, TestHost.calls,
|
|
12069
12509
|
TestHost.sent, TestHost.conflicts,
|
|
12070
12510
|
TestHost.layout, TestHost.menus, and many more.
|
|
@@ -12658,7 +13098,7 @@ class TestHost implements Transport {
|
|
|
12658
13098
|
*/
|
|
12659
13099
|
setVideoRenderOwner(pluginId: string | null): void;
|
|
12660
13100
|
/**
|
|
12661
|
-
* What the player pushes when mpv cannot compile a shader (code `failed`) or switches to software rendering (`unsupported`, which also sets `videoRender.gpu` to false). Pushed only when this plugin is the owner.
|
|
13101
|
+
* What the player pushes when mpv cannot compile a shader (code `failed`) or switches to software rendering (`unsupported`, which also sets `videoRender.gpu` to false and `rendering.reason` to `window-fallback` with `message`). Pushed only when this plugin is the owner.
|
|
12662
13102
|
* @capability None.
|
|
12663
13103
|
* @example Make a shader fail to compile
|
|
12664
13104
|
* The plugin's render options report `failed`, as when mpv rejects a
|
|
@@ -12884,9 +13324,9 @@ class TestHost implements Transport {
|
|
|
12884
13324
|
*/
|
|
12885
13325
|
readonly sessionStorage: Map<string, Json>;
|
|
12886
13326
|
/**
|
|
12887
|
-
* What `plugin.app.system()` reports. Its `rendering` follows videoRender.gpu (`"gpu"` or `"software"`), so one switch covers the render options and the fact.
|
|
13327
|
+
* What `plugin.app.system()` reports. Its `rendering` follows videoRender.gpu (`"gpu"` or `"software"`), so one switch covers the render options and the fact; its `renderingReason` is rendering.reason.
|
|
12888
13328
|
*/
|
|
12889
|
-
systemInfo: Omit<WireSystemInfo, "rendering">;
|
|
13329
|
+
systemInfo: Omit<WireSystemInfo, "rendering" | "renderingReason">;
|
|
12890
13330
|
/** What `plugin.app` reports. Set it before startPlugin: the plugin receives it once at start-up. */
|
|
12891
13331
|
app: {
|
|
12892
13332
|
/** The player version. */
|
|
@@ -12903,6 +13343,70 @@ class TestHost implements Transport {
|
|
|
12903
13343
|
slint: string;
|
|
12904
13344
|
};
|
|
12905
13345
|
};
|
|
13346
|
+
/**
|
|
13347
|
+
* The player's rendering choice behind `plugin.rendering` (player
|
|
13348
|
+
* 0.5.13). The mode drawing now is videoRender.gpu, as for `plugin.app.system()`; the rest is preset here
|
|
13349
|
+
* and changed by the plugin's calls, as the player would:
|
|
13350
|
+
*
|
|
13351
|
+
* - `setPreference` saves `preference` and never changes the mode. It
|
|
13352
|
+
* refuses `"gpu"` while `gpu.available` is false: with `unsupported`
|
|
13353
|
+
* when `gpu.reason.code` is `no-gpu-backend` or `headless`, otherwise
|
|
13354
|
+
* with `unavailable`, the reason's message in both cases.
|
|
13355
|
+
* - The next start uses `"software"` for the `"software"` choice, else
|
|
13356
|
+
* software with `gpu.reason` while the GPU is not available, else the
|
|
13357
|
+
* GPU (code `preference` for a `"gpu"` choice, `default` for
|
|
13358
|
+
* `"auto"`). `restartPending` compares that with `startedWith`.
|
|
13359
|
+
* - `restartPlayer` counts in `restarts` and leaves the plugin running
|
|
13360
|
+
* (call `stop()` yourself); a second one rejects with `busy`, as a
|
|
13361
|
+
* restart is then under way.
|
|
13362
|
+
*
|
|
13363
|
+
* Inject other refusals (`failed` for an unwritable settings file or a
|
|
13364
|
+
* new process that could not start) with `host.handle("rendering.setPreference", …)`
|
|
13365
|
+
* or `host.handle("rendering.restart", …)`. The defaults are a macOS
|
|
13366
|
+
* player on the GPU: `"auto"`, available, started on the GPU.
|
|
13367
|
+
* @example Test a GPU switch on a system without a GPU renderer
|
|
13368
|
+
* The plugin's GPU menu item is greyed out with the player's reason when
|
|
13369
|
+
* the GPU cannot be chosen. The host is preset before the plugin starts,
|
|
13370
|
+
* because the plugin reads the state while activating; software
|
|
13371
|
+
* rendering is `videoRender.gpu = false`, and `gpu` says why the GPU
|
|
13372
|
+
* cannot be chosen.
|
|
13373
|
+
* ```ts
|
|
13374
|
+
* import assert from "node:assert/strict";
|
|
13375
|
+
* import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
|
|
13376
|
+
* import activate from "../main.ts";
|
|
13377
|
+
*
|
|
13378
|
+
* const host = new TestHost();
|
|
13379
|
+
* host.videoRender.gpu = false; // software draws now (Windows)
|
|
13380
|
+
* host.rendering.startedWith = "software";
|
|
13381
|
+
* host.rendering.reason = { code: "no-gpu-backend", message: "No GPU renderer on Windows" };
|
|
13382
|
+
* host.rendering.gpu = { available: false, reason: host.rendering.reason };
|
|
13383
|
+
* const { stop } = await startPlugin(activate, {
|
|
13384
|
+
* directory: new URL("..", import.meta.url),
|
|
13385
|
+
* host,
|
|
13386
|
+
* });
|
|
13387
|
+
* const item = host.command("gpu-rendering");
|
|
13388
|
+
* assert.equal(item.state.enabled, false);
|
|
13389
|
+
* assert.equal(item.state.reason, "No GPU renderer on Windows");
|
|
13390
|
+
* await stop();
|
|
13391
|
+
* ```
|
|
13392
|
+
*/
|
|
13393
|
+
readonly rendering: {
|
|
13394
|
+
/** Why the mode drawing now is what it is (`RenderingState.reason`, `SystemInfo.renderingReason`). */
|
|
13395
|
+
reason: RenderingReason;
|
|
13396
|
+
/** The saved choice: `"auto"`, `"gpu"` or `"software"`. */
|
|
13397
|
+
preference: RenderingPreference;
|
|
13398
|
+
/** The mode this run started with; `restartPending` compares the next start's mode with it. */
|
|
13399
|
+
startedWith: RenderingMode;
|
|
13400
|
+
/** Whether the plugin may choose `"gpu"`, and why not (`null` while available). */
|
|
13401
|
+
gpu: {
|
|
13402
|
+
/** `false`: `setPreference("gpu")` is refused. */
|
|
13403
|
+
available: boolean;
|
|
13404
|
+
/** Why the GPU cannot be chosen; `null` while it can. */
|
|
13405
|
+
reason: RenderingReason | null;
|
|
13406
|
+
};
|
|
13407
|
+
/** Accepted `restartPlayer` calls. */
|
|
13408
|
+
restarts: number;
|
|
13409
|
+
};
|
|
12906
13410
|
/** What `player.chapters()` lists while media is ready. */
|
|
12907
13411
|
chapters: {
|
|
12908
13412
|
/** The chapter title, `null` without one. */
|
|
@@ -13591,8 +14095,8 @@ current.
|
|
|
13591
14095
|
|
|
13592
14096
|
A member not listed under any capability needs none (`plugin.tools`,
|
|
13593
14097
|
`plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui`,
|
|
13594
|
-
`plugin.i18n`, reading `plugin.window.state`, `plugin.appearance.theme
|
|
13595
|
-
|
|
14098
|
+
`plugin.i18n`, reading `plugin.window.state`, `plugin.appearance.theme`,
|
|
14099
|
+
`plugin.packages.list` and `plugin.rendering.state`).
|
|
13596
14100
|
|
|
13597
14101
|
```ts
|
|
13598
14102
|
const CAPABILITY_INFO: Readonly<Record<string, CapabilityInfo>>;
|
|
@@ -13826,7 +14330,10 @@ menu last): their key paths, default bindings and owners. Use it to pick
|
|
|
13826
14330
|
a menu place and default keys that do not collide with the player's (the
|
|
13827
14331
|
user's own changes come on top; see Bindings). Menu items built
|
|
13828
14332
|
from content (tracks, recent files, output devices) are not listed; their
|
|
13829
|
-
placeholders (`No Audio Tracks`) are.
|
|
14333
|
+
placeholders (`No Audio Tracks`) are. Items that appear only in a
|
|
14334
|
+
passing state are not listed either: the Picture Adjustments plugin's
|
|
14335
|
+
`Video ▸ Restart Player to Apply` (`video.gpu-rendering.restart`), shown
|
|
14336
|
+
while a rendering choice waits for the next start. The player's tests check that this
|
|
13830
14337
|
list agrees with what the player and its built-in plugins register.
|
|
13831
14338
|
|
|
13832
14339
|
```ts
|
|
@@ -14685,6 +15192,8 @@ interface WireSystemInfo {
|
|
|
14685
15192
|
}[];
|
|
14686
15193
|
/** Player 0.5.12; older players leave it out. */
|
|
14687
15194
|
rendering?: "gpu" | "software";
|
|
15195
|
+
/** Why `rendering` is what it is (player 0.5.13; older players leave it out). */
|
|
15196
|
+
renderingReason?: WireRenderingReason;
|
|
14688
15197
|
}
|
|
14689
15198
|
```
|
|
14690
15199
|
|