@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/dist/plugin/testing.js
CHANGED
|
@@ -268,7 +268,7 @@ const MAX_SIDEBAR_TABS = 8;
|
|
|
268
268
|
* {@link TestHost.app}, {@link TestHost.systemInfo},
|
|
269
269
|
* {@link TestHost.chapters}, {@link TestHost.audioDevices},
|
|
270
270
|
* {@link TestHost.secrets}, {@link TestHost.clipboard},
|
|
271
|
-
* {@link TestHost.tools}.
|
|
271
|
+
* {@link TestHost.tools}, {@link TestHost.rendering}.
|
|
272
272
|
* - **Assert** on records: {@link TestHost.osd}, {@link TestHost.calls},
|
|
273
273
|
* {@link TestHost.sent}, {@link TestHost.conflicts},
|
|
274
274
|
* {@link TestHost.layout}, {@link TestHost.menus}, and many more.
|
|
@@ -1267,7 +1267,8 @@ export class TestHost {
|
|
|
1267
1267
|
/**
|
|
1268
1268
|
* What the player pushes when mpv cannot compile a shader (code `failed`)
|
|
1269
1269
|
* or switches to software rendering (`unsupported`, which also sets
|
|
1270
|
-
* `videoRender.gpu` to false
|
|
1270
|
+
* `videoRender.gpu` to false and `rendering.reason` to `window-fallback`
|
|
1271
|
+
* with `message`). Pushed only when this plugin is the owner.
|
|
1271
1272
|
*
|
|
1272
1273
|
* @example Make a shader fail to compile
|
|
1273
1274
|
* The plugin's render options report `failed`, as when mpv rejects a
|
|
@@ -1290,8 +1291,10 @@ export class TestHost {
|
|
|
1290
1291
|
* @since 1.0.0
|
|
1291
1292
|
*/
|
|
1292
1293
|
failVideoRender(code, message) {
|
|
1293
|
-
if (code === "unsupported")
|
|
1294
|
+
if (code === "unsupported") {
|
|
1294
1295
|
this.videoRender.gpu = false;
|
|
1296
|
+
this.rendering.reason = { code: "window-fallback", message };
|
|
1297
|
+
}
|
|
1295
1298
|
if (this.videoRender.owner === this.manifestId)
|
|
1296
1299
|
this.emit("video.render.status", {
|
|
1297
1300
|
state: "failed",
|
|
@@ -1527,7 +1530,8 @@ export class TestHost {
|
|
|
1527
1530
|
/**
|
|
1528
1531
|
* What `plugin.app.system()` reports. Its `rendering` follows
|
|
1529
1532
|
* {@link TestHost.videoRender | videoRender.gpu} (`"gpu"` or
|
|
1530
|
-
* `"software"`), so one switch covers the render options and the fact
|
|
1533
|
+
* `"software"`), so one switch covers the render options and the fact;
|
|
1534
|
+
* its `renderingReason` is {@link TestHost.rendering | rendering.reason}.
|
|
1531
1535
|
*/
|
|
1532
1536
|
systemInfo = {
|
|
1533
1537
|
os: "macos",
|
|
@@ -1560,6 +1564,64 @@ export class TestHost {
|
|
|
1560
1564
|
slint: "1.18.1",
|
|
1561
1565
|
},
|
|
1562
1566
|
};
|
|
1567
|
+
/**
|
|
1568
|
+
* The player's rendering choice behind `plugin.rendering` (player
|
|
1569
|
+
* 0.5.13). The mode drawing now is {@link TestHost.videoRender |
|
|
1570
|
+
* videoRender.gpu}, as for `plugin.app.system()`; the rest is preset here
|
|
1571
|
+
* and changed by the plugin's calls, as the player would:
|
|
1572
|
+
*
|
|
1573
|
+
* - `setPreference` saves `preference` and never changes the mode. It
|
|
1574
|
+
* refuses `"gpu"` while `gpu.available` is false: with `unsupported`
|
|
1575
|
+
* when `gpu.reason.code` is `no-gpu-backend` or `headless`, otherwise
|
|
1576
|
+
* with `unavailable`, the reason's message in both cases.
|
|
1577
|
+
* - The next start uses `"software"` for the `"software"` choice, else
|
|
1578
|
+
* software with `gpu.reason` while the GPU is not available, else the
|
|
1579
|
+
* GPU (code `preference` for a `"gpu"` choice, `default` for
|
|
1580
|
+
* `"auto"`). `restartPending` compares that with `startedWith`.
|
|
1581
|
+
* - `restartPlayer` counts in `restarts` and leaves the plugin running
|
|
1582
|
+
* (call `stop()` yourself); a second one rejects with `busy`, as a
|
|
1583
|
+
* restart is then under way.
|
|
1584
|
+
*
|
|
1585
|
+
* Inject other refusals (`failed` for an unwritable settings file or a
|
|
1586
|
+
* new process that could not start) with `host.handle("rendering.setPreference", …)`
|
|
1587
|
+
* or `host.handle("rendering.restart", …)`. The defaults are a macOS
|
|
1588
|
+
* player on the GPU: `"auto"`, available, started on the GPU.
|
|
1589
|
+
*
|
|
1590
|
+
* @example Test a GPU switch on a system without a GPU renderer
|
|
1591
|
+
* The plugin's GPU menu item is greyed out with the player's reason when
|
|
1592
|
+
* the GPU cannot be chosen. The host is preset before the plugin starts,
|
|
1593
|
+
* because the plugin reads the state while activating; software
|
|
1594
|
+
* rendering is `videoRender.gpu = false`, and `gpu` says why the GPU
|
|
1595
|
+
* cannot be chosen.
|
|
1596
|
+
* ```ts
|
|
1597
|
+
* import assert from "node:assert/strict";
|
|
1598
|
+
* import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
|
|
1599
|
+
* import activate from "../main.ts";
|
|
1600
|
+
*
|
|
1601
|
+
* const host = new TestHost();
|
|
1602
|
+
* host.videoRender.gpu = false; // software draws now (Windows)
|
|
1603
|
+
* host.rendering.startedWith = "software";
|
|
1604
|
+
* host.rendering.reason = { code: "no-gpu-backend", message: "No GPU renderer on Windows" };
|
|
1605
|
+
* host.rendering.gpu = { available: false, reason: host.rendering.reason };
|
|
1606
|
+
* const { stop } = await startPlugin(activate, {
|
|
1607
|
+
* directory: new URL("..", import.meta.url),
|
|
1608
|
+
* host,
|
|
1609
|
+
* });
|
|
1610
|
+
* const item = host.command("gpu-rendering");
|
|
1611
|
+
* assert.equal(item.state.enabled, false);
|
|
1612
|
+
* assert.equal(item.state.reason, "No GPU renderer on Windows");
|
|
1613
|
+
* await stop();
|
|
1614
|
+
* ```
|
|
1615
|
+
*
|
|
1616
|
+
* @since 1.5.35
|
|
1617
|
+
*/
|
|
1618
|
+
rendering = {
|
|
1619
|
+
reason: { code: "default", message: "nothing rules GPU rendering out" },
|
|
1620
|
+
preference: "auto",
|
|
1621
|
+
startedWith: "gpu",
|
|
1622
|
+
gpu: { available: true, reason: null },
|
|
1623
|
+
restarts: 0,
|
|
1624
|
+
};
|
|
1563
1625
|
/** What `player.chapters()` lists while media is ready. */
|
|
1564
1626
|
chapters = [];
|
|
1565
1627
|
#overrides = new Map();
|
|
@@ -3190,6 +3252,54 @@ export class TestHost {
|
|
|
3190
3252
|
revision: stamp.revision + 1,
|
|
3191
3253
|
};
|
|
3192
3254
|
}
|
|
3255
|
+
/** `rendering.state` from {@link TestHost.rendering} and `videoRender.gpu`. */
|
|
3256
|
+
#renderingState() {
|
|
3257
|
+
const { reason, preference, startedWith, gpu } = this.rendering;
|
|
3258
|
+
// The player's order (render_mode.rs decide): no windows or no GPU
|
|
3259
|
+
// renderer first, then the preference `software`, then what keeps GPU
|
|
3260
|
+
// rendering off (denylist, crash), else GPU with the reason `default`
|
|
3261
|
+
// (also when the preference is `gpu`).
|
|
3262
|
+
const system = !gpu.available &&
|
|
3263
|
+
(gpu.reason?.code === "headless" ||
|
|
3264
|
+
gpu.reason?.code === "no-gpu-backend");
|
|
3265
|
+
const next = system
|
|
3266
|
+
? { mode: "software", reason: gpu.reason }
|
|
3267
|
+
: preference === "software"
|
|
3268
|
+
? {
|
|
3269
|
+
mode: "software",
|
|
3270
|
+
reason: {
|
|
3271
|
+
code: "preference",
|
|
3272
|
+
message: "chosen in the player's settings",
|
|
3273
|
+
},
|
|
3274
|
+
}
|
|
3275
|
+
: !gpu.available
|
|
3276
|
+
? {
|
|
3277
|
+
mode: "software",
|
|
3278
|
+
reason: gpu.reason ?? {
|
|
3279
|
+
code: "default",
|
|
3280
|
+
message: "nothing rules GPU rendering out",
|
|
3281
|
+
},
|
|
3282
|
+
}
|
|
3283
|
+
: {
|
|
3284
|
+
mode: "gpu",
|
|
3285
|
+
reason: {
|
|
3286
|
+
code: "default",
|
|
3287
|
+
message: "nothing rules GPU rendering out",
|
|
3288
|
+
},
|
|
3289
|
+
};
|
|
3290
|
+
return {
|
|
3291
|
+
mode: this.videoRender.gpu ? "gpu" : "software",
|
|
3292
|
+
reason: { ...reason },
|
|
3293
|
+
preference,
|
|
3294
|
+
nextMode: next.mode,
|
|
3295
|
+
nextReason: { ...next.reason },
|
|
3296
|
+
restartPending: next.mode !== startedWith,
|
|
3297
|
+
gpu: {
|
|
3298
|
+
available: gpu.available,
|
|
3299
|
+
reason: gpu.available || !gpu.reason ? null : { ...gpu.reason },
|
|
3300
|
+
},
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3193
3303
|
#defaults = {
|
|
3194
3304
|
"events.subscribe": (p) => void this.subscriptions.add(String(p.event)),
|
|
3195
3305
|
"events.unsubscribe": (p) => void this.subscriptions.delete(String(p.event)),
|
|
@@ -3506,7 +3616,30 @@ export class TestHost {
|
|
|
3506
3616
|
"app.system": () => ({
|
|
3507
3617
|
...structuredClone(this.systemInfo),
|
|
3508
3618
|
rendering: this.videoRender.gpu ? "gpu" : "software",
|
|
3619
|
+
renderingReason: { ...this.rendering.reason },
|
|
3509
3620
|
}),
|
|
3621
|
+
// As the player 0.5.13 (see TestHost.rendering for the model).
|
|
3622
|
+
"rendering.state": () => this.#renderingState(),
|
|
3623
|
+
"rendering.setPreference": (p) => {
|
|
3624
|
+
const preference = p.preference;
|
|
3625
|
+
if (preference !== "auto" &&
|
|
3626
|
+
preference !== "gpu" &&
|
|
3627
|
+
preference !== "software")
|
|
3628
|
+
throw new HostError("invalid", 'preference must be "auto", "gpu" or "software"');
|
|
3629
|
+
const { available, reason } = this.rendering.gpu;
|
|
3630
|
+
if (preference === "gpu" && !available)
|
|
3631
|
+
throw new HostError(reason?.code === "no-gpu-backend" || reason?.code === "headless"
|
|
3632
|
+
? "unsupported"
|
|
3633
|
+
: "unavailable", reason?.message ?? "GPU rendering cannot be chosen");
|
|
3634
|
+
this.rendering.preference = preference;
|
|
3635
|
+
return this.#renderingState();
|
|
3636
|
+
},
|
|
3637
|
+
"rendering.restart": () => {
|
|
3638
|
+
if (this.rendering.restarts > 0)
|
|
3639
|
+
throw new HostError("busy", "a restart is already under way");
|
|
3640
|
+
this.rendering.restarts++;
|
|
3641
|
+
return null;
|
|
3642
|
+
},
|
|
3510
3643
|
"clipboard.readText": () => this.clipboard.text,
|
|
3511
3644
|
"clipboard.writeText": (p) => {
|
|
3512
3645
|
if (typeof p.text !== "string")
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -293,6 +293,8 @@ export interface Scope extends Disposable {
|
|
|
293
293
|
readonly window: MainWindow;
|
|
294
294
|
/** The player's version and components, and the system it runs on. */
|
|
295
295
|
readonly app: App;
|
|
296
|
+
/** GPU or software rendering: read the choice, change it (`rendering.manage`). */
|
|
297
|
+
readonly rendering: Rendering;
|
|
296
298
|
/** Every command's bindings and the user's overrides (`bindings.manage`). */
|
|
297
299
|
readonly bindings: Bindings;
|
|
298
300
|
/** Runtime images for Slint `image` properties. */
|
|
@@ -4075,6 +4077,31 @@ export interface SystemInfo {
|
|
|
4075
4077
|
* @since 1.5.34
|
|
4076
4078
|
*/
|
|
4077
4079
|
readonly rendering?: "gpu" | "software";
|
|
4080
|
+
/**
|
|
4081
|
+
* Why {@link SystemInfo.rendering} is what it is now: the same reason as
|
|
4082
|
+
* {@link RenderingState.reason} (`default`, `no-gpu-backend`,
|
|
4083
|
+
* `preference`, `denylist`, `window-fallback`…, see
|
|
4084
|
+
* {@link RenderingReasonCode}). Read at each call, like `rendering`.
|
|
4085
|
+
*
|
|
4086
|
+
* Absent (`undefined`) on players before 0.5.13.
|
|
4087
|
+
*
|
|
4088
|
+
* @example Say why the video is drawn in software
|
|
4089
|
+
* An About page explains a software renderer with the player's own words,
|
|
4090
|
+
* for example that the graphics driver is on the player's denylist.
|
|
4091
|
+
* `system()` reports the renderer and its reason together, with no
|
|
4092
|
+
* capability; the message is English text that may name the adapter and
|
|
4093
|
+
* the driver version.
|
|
4094
|
+
* ```ts
|
|
4095
|
+
* const { rendering, renderingReason } = await plugin.app.system();
|
|
4096
|
+
* if (rendering === "software" && renderingReason)
|
|
4097
|
+
* console.log(`Rendering: software (${renderingReason.message})`);
|
|
4098
|
+
* ```
|
|
4099
|
+
*
|
|
4100
|
+
* @player 0.5.13; older players leave it out (declare
|
|
4101
|
+
* `"minPlayerVersion": "0.5.13"` when the plugin relies on it).
|
|
4102
|
+
* @since 1.5.35
|
|
4103
|
+
*/
|
|
4104
|
+
readonly renderingReason?: RenderingReason;
|
|
4078
4105
|
}
|
|
4079
4106
|
/**
|
|
4080
4107
|
* One display in {@link SystemInfo.displays}.
|
|
@@ -8934,3 +8961,329 @@ export interface Secrets {
|
|
|
8934
8961
|
*/
|
|
8935
8962
|
delete(name: string): Promise<void>;
|
|
8936
8963
|
}
|
|
8964
|
+
/**
|
|
8965
|
+
* Which renderer draws the video: `"gpu"` (the GPU renderer, where
|
|
8966
|
+
* {@link RenderOptions | render options} apply) or `"software"` (they fail
|
|
8967
|
+
* with `unsupported`). The same values as {@link SystemInfo.rendering}.
|
|
8968
|
+
*
|
|
8969
|
+
* @example Tell the user which renderer draws the video
|
|
8970
|
+
* The plugin shows "Rendering: GPU" or "Rendering: software" on the
|
|
8971
|
+
* video. `state()` asks the player; `mode` is the renderer drawing now,
|
|
8972
|
+
* which can differ from the saved choice until the next start.
|
|
8973
|
+
* ```ts
|
|
8974
|
+
* const { mode } = await plugin.rendering.state();
|
|
8975
|
+
* const label: Record<RenderingMode, string> = { gpu: "GPU", software: "software" };
|
|
8976
|
+
* await plugin.osd.show("Rendering: {}", [label[mode]]);
|
|
8977
|
+
* ```
|
|
8978
|
+
*
|
|
8979
|
+
* @player 0.5.13
|
|
8980
|
+
* @since 1.5.35
|
|
8981
|
+
* @category Services
|
|
8982
|
+
*/
|
|
8983
|
+
export type RenderingMode = "gpu" | "software";
|
|
8984
|
+
/**
|
|
8985
|
+
* The user's saved rendering choice: `"auto"` (the player decides: the GPU
|
|
8986
|
+
* where this system has a GPU renderer that is not ruled out, software
|
|
8987
|
+
* elsewhere), `"gpu"` or `"software"`. It applies from the next start.
|
|
8988
|
+
*
|
|
8989
|
+
* @example Describe the saved choice on a settings page
|
|
8990
|
+
* The plugin turns the saved choice into the words its settings page
|
|
8991
|
+
* shows. `preference` is what the user chose, not what draws now:
|
|
8992
|
+
* {@link RenderingState.nextMode} says what the next start will use.
|
|
8993
|
+
* ```ts
|
|
8994
|
+
* const { preference } = await plugin.rendering.state();
|
|
8995
|
+
* // One word per choice; the Record type makes sure none is missing.
|
|
8996
|
+
* const words: Record<RenderingPreference, string> = {
|
|
8997
|
+
* auto: "Automatic",
|
|
8998
|
+
* gpu: "GPU",
|
|
8999
|
+
* software: "Software",
|
|
9000
|
+
* };
|
|
9001
|
+
* console.log(words[preference]);
|
|
9002
|
+
* ```
|
|
9003
|
+
*
|
|
9004
|
+
* @player 0.5.13
|
|
9005
|
+
* @since 1.5.35
|
|
9006
|
+
* @category Services
|
|
9007
|
+
*/
|
|
9008
|
+
export type RenderingPreference = "auto" | "gpu" | "software";
|
|
9009
|
+
/**
|
|
9010
|
+
* Why the player renders the way it does, as a stable kebab-case code. A
|
|
9011
|
+
* newer player may add codes: compare with the ones you handle and show
|
|
9012
|
+
* {@link RenderingReason.message} for the rest. The codes of player 0.5.13:
|
|
9013
|
+
*
|
|
9014
|
+
* | Code | Meaning |
|
|
9015
|
+
* | --- | --- |
|
|
9016
|
+
* | `default` | Nothing ruled the GPU out; the platform's default. |
|
|
9017
|
+
* | `pending` | GPU rendering is starting. |
|
|
9018
|
+
* | `headless` | The player runs without a user interface. |
|
|
9019
|
+
* | `no-gpu-backend` | This player has no GPU renderer on this system (Windows today). |
|
|
9020
|
+
* | `env` | The environment chose it (`VESTRA_RENDER`, `VESTRA_SOFTWARE_RENDER`). |
|
|
9021
|
+
* | `preference` | The user chose software rendering ({@link Rendering.setPreference}); GPU chosen and available reports `default`. |
|
|
9022
|
+
* | `denylist` | The graphics driver is on the player's denylist. |
|
|
9023
|
+
* | `previous-crash` | GPU rendering crashed at an earlier start; it is tried again after a driver or player update, or after 7 days. |
|
|
9024
|
+
* | `init-failed` | GPU rendering could not start. |
|
|
9025
|
+
* | `window-fallback` | A window could not show GPU frames; the player switched to software during the run. |
|
|
9026
|
+
*
|
|
9027
|
+
* @example Explain a driver problem differently from a user choice
|
|
9028
|
+
* The plugin adds advice only when the graphics driver is the cause.
|
|
9029
|
+
* `reason.code` is looked up among the codes the plugin knows; an unknown
|
|
9030
|
+
* code from a newer player falls through to the player's own message.
|
|
9031
|
+
* ```ts
|
|
9032
|
+
* const { reason } = await plugin.rendering.state();
|
|
9033
|
+
* // Advice for the codes this plugin knows; any other code shows the message.
|
|
9034
|
+
* const advice: Partial<Record<RenderingReasonCode, string>> = {
|
|
9035
|
+
* denylist: "Update the graphics driver to use the GPU.",
|
|
9036
|
+
* "previous-crash": "The GPU renderer crashed earlier; it is retried after an update.",
|
|
9037
|
+
* };
|
|
9038
|
+
* console.log(advice[reason.code] ?? reason.message);
|
|
9039
|
+
* ```
|
|
9040
|
+
*
|
|
9041
|
+
* @player 0.5.13
|
|
9042
|
+
* @since 1.5.35
|
|
9043
|
+
* @category Services
|
|
9044
|
+
*/
|
|
9045
|
+
export type RenderingReasonCode = "default" | "pending" | "headless" | "no-gpu-backend" | "env" | "preference" | "denylist" | "previous-crash" | "init-failed" | "window-fallback" | (string & {});
|
|
9046
|
+
/**
|
|
9047
|
+
* Why the player renders, or will render, the way it does: a stable
|
|
9048
|
+
* {@link RenderingReasonCode} to branch on and a sentence to show.
|
|
9049
|
+
*
|
|
9050
|
+
* @example Show why GPU rendering is off
|
|
9051
|
+
* The plugin logs the player's explanation when the video is drawn in
|
|
9052
|
+
* software. `message` is the player's English text and may name the
|
|
9053
|
+
* graphics adapter and driver version; `code` is for decisions.
|
|
9054
|
+
* ```ts
|
|
9055
|
+
* const { mode, reason } = await plugin.rendering.state();
|
|
9056
|
+
* if (mode === "software") console.log(`${reason.code}: ${reason.message}`);
|
|
9057
|
+
* ```
|
|
9058
|
+
*
|
|
9059
|
+
* @player 0.5.13
|
|
9060
|
+
* @since 1.5.35
|
|
9061
|
+
* @category Services
|
|
9062
|
+
*/
|
|
9063
|
+
export interface RenderingReason {
|
|
9064
|
+
/** The reason as a stable kebab-case code; branch on this. */
|
|
9065
|
+
readonly code: RenderingReasonCode;
|
|
9066
|
+
/**
|
|
9067
|
+
* The reason as English text for people, for example with the graphics
|
|
9068
|
+
* adapter's name and driver version; never branch on it.
|
|
9069
|
+
*/
|
|
9070
|
+
readonly message: string;
|
|
9071
|
+
}
|
|
9072
|
+
/**
|
|
9073
|
+
* What {@link Rendering.state} and {@link Rendering.setPreference} report:
|
|
9074
|
+
* the renderer drawing now and why, the saved choice, what the next start
|
|
9075
|
+
* will use, and whether `"gpu"` can be chosen.
|
|
9076
|
+
*
|
|
9077
|
+
* @example Label a menu item while a change waits for a restart
|
|
9078
|
+
* The plugin adds "(Restart to Apply)" to its GPU item after the user
|
|
9079
|
+
* changed the choice. `restartPending` compares the mode the next start
|
|
9080
|
+
* will use with the mode this run started with, so it turns false again
|
|
9081
|
+
* when the user changes the choice back.
|
|
9082
|
+
* ```ts
|
|
9083
|
+
* const state = await plugin.rendering.state();
|
|
9084
|
+
* const title = state.restartPending
|
|
9085
|
+
* ? "GPU Rendering (Experimental) (Restart to Apply)"
|
|
9086
|
+
* : "GPU Rendering (Experimental)";
|
|
9087
|
+
* console.log(title, state.nextMode, state.nextReason.message);
|
|
9088
|
+
* ```
|
|
9089
|
+
*
|
|
9090
|
+
* @player 0.5.13
|
|
9091
|
+
* @since 1.5.35
|
|
9092
|
+
* @category Services
|
|
9093
|
+
*/
|
|
9094
|
+
export interface RenderingState {
|
|
9095
|
+
/** The renderer drawing the video now; the same as {@link SystemInfo.rendering}. */
|
|
9096
|
+
readonly mode: RenderingMode;
|
|
9097
|
+
/** Why {@link RenderingState.mode} is what it is now. */
|
|
9098
|
+
readonly reason: RenderingReason;
|
|
9099
|
+
/** The saved choice; it applies from the next start. */
|
|
9100
|
+
readonly preference: RenderingPreference;
|
|
9101
|
+
/**
|
|
9102
|
+
* The renderer the next start will use, as far as the player knows now:
|
|
9103
|
+
* from the saved choice, the environment, the driver denylist and an
|
|
9104
|
+
* earlier GPU crash.
|
|
9105
|
+
*/
|
|
9106
|
+
readonly nextMode: RenderingMode;
|
|
9107
|
+
/** Why the next start will use {@link RenderingState.nextMode}. */
|
|
9108
|
+
readonly nextReason: RenderingReason;
|
|
9109
|
+
/**
|
|
9110
|
+
* `true` while {@link RenderingState.nextMode} differs from the mode this
|
|
9111
|
+
* run started with, so a restart would change the renderer; `false` again
|
|
9112
|
+
* when the user changes the choice back.
|
|
9113
|
+
*/
|
|
9114
|
+
readonly restartPending: boolean;
|
|
9115
|
+
/** Whether the user can choose `"gpu"` now. */
|
|
9116
|
+
readonly gpu: {
|
|
9117
|
+
/** `true` when `setPreference("gpu")` would be accepted. */
|
|
9118
|
+
readonly available: boolean;
|
|
9119
|
+
/**
|
|
9120
|
+
* Why `"gpu"` cannot be chosen (`no-gpu-backend`, `headless`,
|
|
9121
|
+
* `denylist`, `previous-crash`…); `null` while it is available.
|
|
9122
|
+
*/
|
|
9123
|
+
readonly reason: RenderingReason | null;
|
|
9124
|
+
};
|
|
9125
|
+
}
|
|
9126
|
+
/**
|
|
9127
|
+
* `plugin.rendering`: the player's rendering choice, GPU or software.
|
|
9128
|
+
* Reading it needs no capability; changing the saved choice and restarting
|
|
9129
|
+
* the player need `rendering.manage`.
|
|
9130
|
+
*
|
|
9131
|
+
* @remarks
|
|
9132
|
+
* The choice works like a browser's hardware acceleration switch: the
|
|
9133
|
+
* player saves it in its own settings at once, and the running player
|
|
9134
|
+
* keeps its renderer. The next start uses it, whether the user restarts
|
|
9135
|
+
* now through {@link Rendering.restartPlayer} or quits and starts the
|
|
9136
|
+
* player later. The player can still rule the GPU out at a start (an
|
|
9137
|
+
* environment variable, a graphics driver on its denylist, a GPU crash at
|
|
9138
|
+
* an earlier start); {@link RenderingState.nextMode} and
|
|
9139
|
+
* {@link RenderingState.nextReason} say what the next start will do.
|
|
9140
|
+
*
|
|
9141
|
+
* The renderer is a choice for the whole player and a restart stops every
|
|
9142
|
+
* plugin, so change it only when the user asks. A plugin that only needs
|
|
9143
|
+
* to know whether render options work reads {@link SystemInfo.rendering}.
|
|
9144
|
+
*
|
|
9145
|
+
* Players before 0.5.13 do not know these requests: `state()` rejects with
|
|
9146
|
+
* `unsupported`, and they do not load a plugin that declares
|
|
9147
|
+
* `rendering.manage` (an unknown capability). Declare
|
|
9148
|
+
* `"minPlayerVersion": "0.5.13"` when the plugin needs this.
|
|
9149
|
+
*
|
|
9150
|
+
* @example Offer GPU rendering as a menu item
|
|
9151
|
+
* Settings ▸ GPU Rendering (Experimental) carries a check mark while the
|
|
9152
|
+
* next start will draw on the GPU, is greyed out with the player's reason
|
|
9153
|
+
* where the GPU cannot be chosen, and saves the other choice when picked.
|
|
9154
|
+
* `state()` reads the player's view once; `setPreference` saves the choice
|
|
9155
|
+
* and resolves with the new state, which the item's bound values read
|
|
9156
|
+
* again after the handler. The how-to guide adds the restart.
|
|
9157
|
+
* ```ts
|
|
9158
|
+
* let state = await plugin.rendering.state();
|
|
9159
|
+
* plugin.commands.add({
|
|
9160
|
+
* id: "gpu-rendering",
|
|
9161
|
+
* path: ["@settings", "GPU Rendering (Experimental)"],
|
|
9162
|
+
* checked: () => state.nextMode === "gpu", // also for "auto" on a GPU system
|
|
9163
|
+
* enabled: () => state.gpu.available,
|
|
9164
|
+
* reason: () => state.gpu.reason?.message ?? null, // shown while disabled
|
|
9165
|
+
* run: async () => {
|
|
9166
|
+
* const choice = state.nextMode === "gpu" ? "software" : "gpu";
|
|
9167
|
+
* state = await plugin.rendering.setPreference(choice);
|
|
9168
|
+
* },
|
|
9169
|
+
* });
|
|
9170
|
+
* ```
|
|
9171
|
+
*
|
|
9172
|
+
* @capability None to read the state; `rendering.manage` to change the
|
|
9173
|
+
* choice or restart the player.
|
|
9174
|
+
* @see [Offer a GPU rendering switch](../../docs/how-to/gpu-rendering.md)
|
|
9175
|
+
* @player 0.5.13; older players reject the requests as unknown
|
|
9176
|
+
* (`unsupported`).
|
|
9177
|
+
* @since 1.5.35
|
|
9178
|
+
* @category Services
|
|
9179
|
+
*/
|
|
9180
|
+
export interface Rendering {
|
|
9181
|
+
/**
|
|
9182
|
+
* Read the renderer drawing now, the saved choice, what the next start
|
|
9183
|
+
* will use and whether `"gpu"` can be chosen. No capability.
|
|
9184
|
+
*
|
|
9185
|
+
* @capability None.
|
|
9186
|
+
* @throws `unsupported` (rejects) A player before 0.5.13, which does not
|
|
9187
|
+
* know the request.
|
|
9188
|
+
*
|
|
9189
|
+
* @example Find out whether the GPU can be chosen
|
|
9190
|
+
* The plugin reads the state once while it starts and keeps it for its
|
|
9191
|
+
* menu. Each call asks the player; `gpu.available` is false, with a
|
|
9192
|
+
* reason, where this player has no GPU renderer, without a user
|
|
9193
|
+
* interface, or when the driver is ruled out. An older player rejects
|
|
9194
|
+
* with `unsupported`, and the plugin then offers nothing.
|
|
9195
|
+
* ```ts
|
|
9196
|
+
* const state = await plugin.rendering.state().catch((error: PluginError) => {
|
|
9197
|
+
* if (error.code === "unsupported") return null; // a player before 0.5.13
|
|
9198
|
+
* throw error;
|
|
9199
|
+
* });
|
|
9200
|
+
* if (state && !state.gpu.available) console.log(state.gpu.reason?.message);
|
|
9201
|
+
* ```
|
|
9202
|
+
*
|
|
9203
|
+
* @since 1.5.35
|
|
9204
|
+
*/
|
|
9205
|
+
state(): Promise<RenderingState>;
|
|
9206
|
+
/**
|
|
9207
|
+
* Save the user's rendering choice. The player writes it to its settings
|
|
9208
|
+
* at once; the running player keeps its renderer, and the next start
|
|
9209
|
+
* uses the choice whether or not the user restarts now. `"auto"` and
|
|
9210
|
+
* `"software"` are always accepted.
|
|
9211
|
+
*
|
|
9212
|
+
* @param preference - `"auto"`, `"gpu"` or `"software"`.
|
|
9213
|
+
* @returns The new state; {@link RenderingState.restartPending} tells
|
|
9214
|
+
* whether a restart would change the renderer.
|
|
9215
|
+
*
|
|
9216
|
+
* @capability rendering.manage
|
|
9217
|
+
* @throws `invalid` (rejects) `preference` is not `"auto"`, `"gpu"` or
|
|
9218
|
+
* `"software"`.
|
|
9219
|
+
* @throws `unsupported` (rejects) `"gpu"` where this player has no GPU
|
|
9220
|
+
* renderer on this system, or runs without a user interface; also any
|
|
9221
|
+
* value on a player before 0.5.13.
|
|
9222
|
+
* @throws `unavailable` (rejects) `"gpu"` while it is ruled out: the
|
|
9223
|
+
* graphics driver is on the player's denylist, or GPU rendering crashed
|
|
9224
|
+
* at an earlier start. The message says which, with the adapter's name
|
|
9225
|
+
* and the driver version.
|
|
9226
|
+
* @throws `failed` (rejects) The player could not write its settings file.
|
|
9227
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
9228
|
+
* `rendering.manage`.
|
|
9229
|
+
*
|
|
9230
|
+
* @example Switch back to the automatic choice
|
|
9231
|
+
* A Reset item returns the renderer to the player's own choice and tells
|
|
9232
|
+
* the user when it takes effect. `setPreference` saves the choice and
|
|
9233
|
+
* resolves with the new state; `restartPending` is true when the next
|
|
9234
|
+
* start will draw differently from this run. A refused save leaves the
|
|
9235
|
+
* old choice in place.
|
|
9236
|
+
* ```ts
|
|
9237
|
+
* try {
|
|
9238
|
+
* const state = await plugin.rendering.setPreference("auto");
|
|
9239
|
+
* if (state.restartPending) await plugin.osd.show("Applies after a restart");
|
|
9240
|
+
* } catch (error) {
|
|
9241
|
+
* // `failed`: the player's settings file could not be written.
|
|
9242
|
+
* await plugin.osd.show("Not saved ({})", [(error as PluginError).code]);
|
|
9243
|
+
* }
|
|
9244
|
+
* ```
|
|
9245
|
+
*
|
|
9246
|
+
* @since 1.5.35
|
|
9247
|
+
*/
|
|
9248
|
+
setPreference(preference: RenderingPreference): Promise<RenderingState>;
|
|
9249
|
+
/**
|
|
9250
|
+
* Restart the player so that a changed choice applies now. Resolves once
|
|
9251
|
+
* the player has accepted; the player then quits normally, which stops
|
|
9252
|
+
* every plugin (this one included), and starts again with the same
|
|
9253
|
+
* plugins. A local file that was open continues at its position, paused
|
|
9254
|
+
* or playing as it was; a stream or another source that is not a file is
|
|
9255
|
+
* not reopened.
|
|
9256
|
+
*
|
|
9257
|
+
* @capability rendering.manage
|
|
9258
|
+
* @throws `unsupported` (rejects) The player runs without a user
|
|
9259
|
+
* interface; also a player before 0.5.13.
|
|
9260
|
+
* @throws `busy` (rejects) A restart is already under way, or this player
|
|
9261
|
+
* was itself started by a restart less than 30 seconds ago (so a plugin
|
|
9262
|
+
* that restarts on activation cannot make a loop).
|
|
9263
|
+
* @throws `failed` (rejects) The new player process could not be started;
|
|
9264
|
+
* the player keeps running.
|
|
9265
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
9266
|
+
* `rendering.manage`.
|
|
9267
|
+
*
|
|
9268
|
+
* @example Restart after asking the user
|
|
9269
|
+
* After the user changed the renderer, the plugin offers to restart now.
|
|
9270
|
+
* `confirm` asks first, because a restart closes every window (it needs
|
|
9271
|
+
* `ui.dialog`); once `restartPlayer` resolves, the player is quitting and
|
|
9272
|
+
* will stop this plugin, so nothing else is started after it.
|
|
9273
|
+
* ```ts
|
|
9274
|
+
* const { confirmed } = await plugin.dialogs.confirm({
|
|
9275
|
+
* message: "Restart the player now?",
|
|
9276
|
+
* detail: "The new renderer is used from the restart on. The video continues where it is.",
|
|
9277
|
+
* confirm: "Restart",
|
|
9278
|
+
* });
|
|
9279
|
+
* if (confirmed)
|
|
9280
|
+
* await plugin.rendering.restartPlayer().catch((error: PluginError) => {
|
|
9281
|
+
* // `failed`: the new player could not start; this one keeps running.
|
|
9282
|
+
* void plugin.osd.show("Could not restart ({})", [error.code]);
|
|
9283
|
+
* });
|
|
9284
|
+
* ```
|
|
9285
|
+
*
|
|
9286
|
+
* @since 1.5.35
|
|
9287
|
+
*/
|
|
9288
|
+
restartPlayer(): Promise<void>;
|
|
9289
|
+
}
|
package/dist/wire.d.ts
CHANGED
|
@@ -315,6 +315,26 @@ export interface WireSystemInfo {
|
|
|
315
315
|
}[];
|
|
316
316
|
/** Player 0.5.12; older players leave it out. */
|
|
317
317
|
rendering?: "gpu" | "software";
|
|
318
|
+
/** Why `rendering` is what it is (player 0.5.13; older players leave it out). */
|
|
319
|
+
renderingReason?: WireRenderingReason;
|
|
320
|
+
}
|
|
321
|
+
/** A rendering reason: a stable kebab-case `code` and English text. */
|
|
322
|
+
export interface WireRenderingReason {
|
|
323
|
+
code: string;
|
|
324
|
+
message: string;
|
|
325
|
+
}
|
|
326
|
+
/** `rendering.state` and `rendering.setPreference` (player 0.5.13). */
|
|
327
|
+
export interface WireRenderingState {
|
|
328
|
+
mode: "gpu" | "software";
|
|
329
|
+
reason: WireRenderingReason;
|
|
330
|
+
preference: "auto" | "gpu" | "software";
|
|
331
|
+
nextMode: "gpu" | "software";
|
|
332
|
+
nextReason: WireRenderingReason;
|
|
333
|
+
restartPending: boolean;
|
|
334
|
+
gpu: {
|
|
335
|
+
available: boolean;
|
|
336
|
+
reason: WireRenderingReason | null;
|
|
337
|
+
};
|
|
318
338
|
}
|
|
319
339
|
/**
|
|
320
340
|
* Template arguments on the wire (`osd.show`, `osd.panel`, `dialogs.confirm`,
|
|
@@ -527,6 +547,15 @@ export declare function createWire(transport: Transport): {
|
|
|
527
547
|
app: {
|
|
528
548
|
system: () => Promise<WireSystemInfo>;
|
|
529
549
|
};
|
|
550
|
+
/**
|
|
551
|
+
* The rendering choice (player 0.5.13): `state` needs no capability,
|
|
552
|
+
* `setPreference` and `restart` need `rendering.manage`.
|
|
553
|
+
*/
|
|
554
|
+
rendering: {
|
|
555
|
+
state: () => Promise<WireRenderingState>;
|
|
556
|
+
setPreference: (preference: string) => Promise<WireRenderingState>;
|
|
557
|
+
restart: () => Promise<null>;
|
|
558
|
+
};
|
|
530
559
|
power: {
|
|
531
560
|
preventSleep: (mode: "off" | "playing") => Promise<void>;
|
|
532
561
|
};
|
package/dist/wire.js
CHANGED
|
@@ -214,6 +214,17 @@ export function createWire(transport) {
|
|
|
214
214
|
app: {
|
|
215
215
|
system: () => transport.request("app.system"),
|
|
216
216
|
},
|
|
217
|
+
/**
|
|
218
|
+
* The rendering choice (player 0.5.13): `state` needs no capability,
|
|
219
|
+
* `setPreference` and `restart` need `rendering.manage`.
|
|
220
|
+
*/
|
|
221
|
+
rendering: {
|
|
222
|
+
state: () => transport.request("rendering.state"),
|
|
223
|
+
setPreference: (preference) => transport.request("rendering.setPreference", {
|
|
224
|
+
preference,
|
|
225
|
+
}),
|
|
226
|
+
restart: () => transport.request("rendering.restart"),
|
|
227
|
+
},
|
|
217
228
|
power: {
|
|
218
229
|
preventSleep: (mode) => transport.request("power.preventSleep", { mode }),
|
|
219
230
|
},
|