@vestra-player/sdk 1.5.35 → 1.5.37

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.37
4
+
5
+ **Who must change: nobody.** Additions only, `apiVersion` stays `vestra/1`. The new API needs player 0.5.16 (`"minPlayerVersion": "0.5.16"`): older players do not load a plugin that declares the new capability `video.upscaling` (an unknown capability). A plugin that does not use it sees no difference.
6
+
7
+ - **`plugin.video.upscaling`** (player 0.5.16): the GPU's video upscaler, NVIDIA RTX Video Super Resolution on Windows. `state()` reports whether it works now (`available`) and why not (`reason`, `{ code, message }` with the stable codes of `UpscalingReasonCode`: `platform`, `no-gpu`, `driver`, `gpu-unsupported`, `rendering`, `adapter`, `decoding`, `unchecked`; a newer player may add codes), whether this plugin turned it on (`enabled`), and whether the player asked the GPU to upscale now (`requested`, with the scale `factor`). `set(enabled)` turns this plugin's request on or off and resolves with the new state; `true` rejects with `unsupported` where upscaling cannot work in this run: every reason except `decoding` (it can pass with the next file) and except `rendering` while GPU rendering is still starting, which are accepted and kept; `false` is always accepted and answered at once without the GPU check (before any check: `available: false` with `unchecked`); `invalid` for a non-boolean. The player checks the GPU once, at the first `state()` or `set(true)` (it wakes a sleeping discrete GPU, so the examples ask on the first pick of the item, not while the plugin starts), puts the upscaler last in the video filter chain scaled to the area the video is shown in (only above 1×), follows size changes, and takes it out when the last plugin turns it off or stops. There is no "upscaling active" signal: on Windows the user must also turn on Super Resolution in the NVIDIA app, which no program can read. New types `VideoUpscaling`, `UpscalingState`, `UpscalingReason`, `UpscalingReasonCode`.
8
+ - **New capability `video.upscaling`** ("Upscale the video with the GPU's video upscaler", not highlighted in the store: it changes only how the picture is scaled while the plugin runs and touches no data), last in `CAPABILITIES`; both requests need it and reject with `capability` without it. The player's capability table copy, `CAPABILITY_INFO`, `vestra-plugin check` and the editor plugin know it.
9
+ - **`TestHost.upscaling`** models the player: preset `reason` (`null`: it works) and `factor`, read `enabled` and `checked` (whether the plugin made the player check the GPU), set `renderingStarting`; `set(true)` refuses with `unsupported` unless the reason is `decoding`, or `rendering` with `renderingStarting`; `set(false)` checks nothing (`unchecked` before any check).
10
+ - _Video and audio processing_ and _Platforms_ describe the upscaler.
11
+
12
+ ## 1.5.36
13
+
14
+ **Who must change: nobody.** No API changes, `apiVersion` stays `vestra/1`, no player version needed.
15
+
16
+ - **`BUILTIN_COMMANDS`** lists the Picture Adjustments plugin's (`vestra.video` 1.1.8) `Video ▸ GPU Rendering (Experimental)` (`video.gpu-rendering`, no default keys), after the color steps. Its `Video ▸ Restart Player to Apply` (`video.gpu-rendering.restart`) appears only while a rendering choice waits for the next start and is not listed, like other items of a passing state; the reference says so.
17
+
3
18
  ## 1.5.35
4
19
 
5
20
  **Who must change: nobody.** Additions only, `apiVersion` stays `vestra/1`. The new API needs player 0.5.13 (`"minPlayerVersion": "0.5.13"`): older players leave `SystemInfo.renderingReason` out, refuse `rendering.state` as an unknown request (`unsupported`), and do not load a plugin that declares the new capability `rendering.manage`. A plugin that uses none of it sees no difference.
@@ -143,4 +143,6 @@ export const API_REQUESTS = Object.freeze({
143
143
  "Rendering.state": [{ method: "rendering.state" }],
144
144
  "Rendering.setPreference": [{ method: "rendering.setPreference" }],
145
145
  "Rendering.restartPlayer": [{ method: "rendering.restart" }],
146
+ "VideoUpscaling.state": [{ method: "video.upscaling.state" }],
147
+ "VideoUpscaling.set": [{ method: "video.upscaling.set" }],
146
148
  });
@@ -205,7 +205,10 @@ export interface BuiltinCommand {
205
205
  * a menu place and default keys that do not collide with the player's (the
206
206
  * user's own changes come on top; see {@link @vestra-player/sdk!Bindings | Bindings}). Menu items built
207
207
  * from content (tracks, recent files, output devices) are not listed; their
208
- * placeholders (`No Audio Tracks`) are. The player's tests check that this
208
+ * placeholders (`No Audio Tracks`) are. Items that appear only in a
209
+ * passing state are not listed either: the Picture Adjustments plugin's
210
+ * `Video ▸ Restart Player to Apply` (`video.gpu-rendering.restart`), shown
211
+ * while a rendering choice waits for the next start. The player's tests check that this
209
212
  * list agrees with what the player and its built-in plugins register.
210
213
  *
211
214
  * @example Check in a test that a default key is still free
package/dist/commands.js CHANGED
@@ -464,6 +464,7 @@ vestra.video | video.saturation.down | Video ▸ Saturation -1% | Y
464
464
  vestra.video | video.saturation.up | Video ▸ Saturation +1% | U
465
465
  vestra.video | video.hue.down | Video ▸ Hue -1% | I
466
466
  vestra.video | video.hue.up | Video ▸ Hue +1% | O
467
+ vestra.video | video.gpu-rendering | Video ▸ GPU Rendering (Experimental)
467
468
  host | volume-up | Audio ▸ Volume + | ArrowUp, Mouse:WheelUp
468
469
  host | volume-down | Audio ▸ Volume − | ArrowDown, Mouse:WheelDown
469
470
  host | mute | Audio ▸ Mute | M
@@ -577,7 +578,10 @@ vestra.playlist | playlist.settings | (Playlist Settings…) | F5 | panel
577
578
  * a menu place and default keys that do not collide with the player's (the
578
579
  * user's own changes come on top; see {@link @vestra-player/sdk!Bindings | Bindings}). Menu items built
579
580
  * from content (tracks, recent files, output devices) are not listed; their
580
- * placeholders (`No Audio Tracks`) are. The player's tests check that this
581
+ * placeholders (`No Audio Tracks`) are. Items that appear only in a
582
+ * passing state are not listed either: the Picture Adjustments plugin's
583
+ * `Video ▸ Restart Player to Apply` (`video.gpu-rendering.restart`), shown
584
+ * while a rendering choice waits for the next start. The player's tests check that this
581
585
  * list agrees with what the player and its built-in plugins register.
582
586
  *
583
587
  * @example Check in a test that a default key is still free
@@ -354,6 +354,17 @@ export const HOST_CAPABILITIES = {
354
354
  residency: "none",
355
355
  risky: true,
356
356
  },
357
+ {
358
+ label: "Upscale the video with the GPU's video upscaler",
359
+ methods: [
360
+ { name: "video.upscaling.state", needs: "video.upscaling" },
361
+ { name: "video.upscaling.set", needs: "video.upscaling" },
362
+ ],
363
+ name: "video.upscaling",
364
+ requires: [],
365
+ residency: "live",
366
+ risky: false,
367
+ },
357
368
  ],
358
369
  uncapable: [
359
370
  { name: "packages.list", needs: null },
@@ -15,6 +15,7 @@ export declare class ScopeImpl implements Plugin {
15
15
  readonly video: {
16
16
  view: () => import("./types.ts").VideoView;
17
17
  render: () => import("./types.ts").RenderOptions;
18
+ upscaling: import("./types.ts").VideoUpscaling;
18
19
  filter(id: string): import("./types.ts").VideoFilter;
19
20
  };
20
21
  readonly audio: {
@@ -17,7 +17,7 @@ import { packages } from "./packages.js";
17
17
  import { osd } from "./osd.js";
18
18
  import { mainWindow } from "./window.js";
19
19
  import { bindingsApi } from "./bindings.js";
20
- import { clipboard, host, power, rendering, secrets, storage, system, tools, } from "./services.js";
20
+ import { clipboard, host, power, rendering, secrets, storage, system, tools, videoUpscaling, } from "./services.js";
21
21
  export class ScopeImpl {
22
22
  runtime;
23
23
  #parent;
@@ -49,6 +49,7 @@ export class ScopeImpl {
49
49
  ...videoFilters(this),
50
50
  view: () => videoView(this),
51
51
  render: () => videoRender(this),
52
+ upscaling: videoUpscaling(this),
52
53
  };
53
54
  audio = audioFilters(this);
54
55
  subtitles = subtitles(this);
@@ -1,5 +1,5 @@
1
1
  import type { ScopeImpl } from "./scope.ts";
2
- import type { Clipboard, Host, Power, Rendering, Secrets, Storage, System, Tools } from "./types.ts";
2
+ import type { Clipboard, Host, Power, Rendering, Secrets, Storage, System, Tools, VideoUpscaling } from "./types.ts";
3
3
  export declare const clipboard: (scope: ScopeImpl) => Clipboard;
4
4
  export declare const host: (scope: ScopeImpl) => Host;
5
5
  export declare const power: (scope: ScopeImpl) => Power;
@@ -113,3 +113,9 @@ export declare const SECRET_LIMITS: Readonly<{
113
113
  }>;
114
114
  export declare const secrets: (scope: ScopeImpl) => Secrets;
115
115
  export declare const rendering: (scope: ScopeImpl) => Rendering;
116
+ /**
117
+ * `plugin.video.upscaling` (player 0.5.16): both requests need
118
+ * `video.upscaling`; the player answers once its GPU check ran (the first
119
+ * call starts it).
120
+ */
121
+ export declare const videoUpscaling: (scope: ScopeImpl) => VideoUpscaling;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Shared services: the clipboard, sleep prevention, showing and opening files
3
- * and links, secrets, session storage and the rendering choice go to the
4
- * host; external tools and settings files run in this plugin's own process.
3
+ * and links, secrets, session storage, the rendering choice and the video
4
+ * upscaler go to the host; external tools and settings files run in this
5
+ * plugin's own process.
5
6
  */
6
7
  import { existsSync, readFileSync, renameSync } from "node:fs";
7
8
  import { mkdir, open, rename, rm, stat } from "node:fs/promises";
@@ -497,3 +498,26 @@ export const rendering = (scope) => {
497
498
  },
498
499
  };
499
500
  };
501
+ /**
502
+ * `plugin.video.upscaling` (player 0.5.16): both requests need
503
+ * `video.upscaling`; the player answers once its GPU check ran (the first
504
+ * call starts it).
505
+ */
506
+ export const videoUpscaling = (scope) => {
507
+ const declared = () => {
508
+ if (!scope.runtime.can("video.upscaling"))
509
+ throw failure("capability", "video.upscaling must be declared in manifest");
510
+ };
511
+ return {
512
+ async state() {
513
+ declared();
514
+ return (await scope.runtime.host.video.upscalingState());
515
+ },
516
+ async set(enabled) {
517
+ declared();
518
+ if (typeof enabled !== "boolean")
519
+ throw failure("invalid", "enabled must be true or false");
520
+ return (await scope.runtime.host.video.upscalingSet(enabled));
521
+ },
522
+ };
523
+ };
@@ -15,7 +15,7 @@ import { IMAGE_LIMITS, UI_VALUE_LIMITS } from "./values.ts";
15
15
  import type { CommandDeclaration } from "../commands.ts";
16
16
  import type { SourceState } from "../source.ts";
17
17
  import type { SubtitleState } from "../subtitles.ts";
18
- import type { RenderingMode, RenderingPreference, RenderingReason, RenderSettings, ViewSettings } from "./types.ts";
18
+ import type { RenderingMode, RenderingPreference, RenderingReason, RenderSettings, UpscalingReason, ViewSettings } from "./types.ts";
19
19
  import type { MediaJob } from "../media.ts";
20
20
  import type { LanguagePreference, Locale, Plugin, TemplateArgs, TextValue, ThemeId } from "./types.ts";
21
21
  import { LOCALES, LIST_SEPARATORS, TEXT_LIMITS } from "../i18n.ts";
@@ -469,7 +469,8 @@ export interface MarkdownWire {
469
469
  * {@link TestHost.app}, {@link TestHost.systemInfo},
470
470
  * {@link TestHost.chapters}, {@link TestHost.audioDevices},
471
471
  * {@link TestHost.secrets}, {@link TestHost.clipboard},
472
- * {@link TestHost.tools}, {@link TestHost.rendering}.
472
+ * {@link TestHost.tools}, {@link TestHost.rendering},
473
+ * {@link TestHost.upscaling}.
473
474
  * - **Assert** on records: {@link TestHost.osd}, {@link TestHost.calls},
474
475
  * {@link TestHost.sent}, {@link TestHost.conflicts},
475
476
  * {@link TestHost.layout}, {@link TestHost.menus}, and many more.
@@ -1703,6 +1704,65 @@ export declare class TestHost implements Transport {
1703
1704
  /** Accepted `restartPlayer` calls. */
1704
1705
  restarts: number;
1705
1706
  };
1707
+ /**
1708
+ * The GPU's video upscaler behind `plugin.video.upscaling` (player
1709
+ * 0.5.16), answered as the player would:
1710
+ *
1711
+ * - `state()` and `set(true)` set `checked` (the player's one GPU check; a
1712
+ * plugin that should not wake the GPU while it starts can assert it is
1713
+ * still `false`) and report `available` from `reason` (`null`: it
1714
+ * works), this plugin's `enabled`, and `requested` with `factor` while
1715
+ * enabled, available and `factor` is above 1.
1716
+ * - `set(true)` refuses with `unsupported` and the reason's message while
1717
+ * `reason` is set, unless its code is `decoding`, or `rendering` with
1718
+ * `renderingStarting` (GPU rendering still starting): those can pass
1719
+ * during the run, so the player keeps the request. Nothing changes when
1720
+ * it refuses.
1721
+ * - `set(false)` always succeeds and does not check the GPU: before any
1722
+ * check it reports `available: false` with the reason `unchecked`.
1723
+ *
1724
+ * Inject other answers with `host.handle("video.upscaling.set", …)`. The
1725
+ * defaults are a player where upscaling works and a 1080p video shown
1726
+ * full screen on a 4K display (`factor` 2), whatever `systemInfo` says.
1727
+ *
1728
+ * @example Test the menu item on a computer without an RTX GPU
1729
+ * The plugin's upscaling item is greyed out with the player's reason after
1730
+ * the user first picks it. Starting the plugin checks nothing (`checked`
1731
+ * stays false, so no GPU wakes up); the first pick reads the state, and
1732
+ * `set(true)` would reject with `unsupported`, as in the player.
1733
+ * ```ts
1734
+ * import assert from "node:assert/strict";
1735
+ * import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
1736
+ * import activate from "../main.ts";
1737
+ *
1738
+ * const host = new TestHost();
1739
+ * host.upscaling.reason = { code: "no-gpu", message: "no NVIDIA GPU" };
1740
+ * const { stop } = await startPlugin(activate, {
1741
+ * directory: new URL("..", import.meta.url),
1742
+ * host,
1743
+ * });
1744
+ * assert.equal(host.upscaling.checked, false); // starting woke no GPU
1745
+ * const item = host.command("upscaling");
1746
+ * await item.run(); // the first pick checks the GPU
1747
+ * assert.equal(item.state.enabled, false); // greyed out
1748
+ * assert.equal(item.state.reason, "no NVIDIA GPU"); // the player's message
1749
+ * await stop();
1750
+ * ```
1751
+ *
1752
+ * @since 1.5.37
1753
+ */
1754
+ readonly upscaling: {
1755
+ /** Why upscaling does not work; `null` while it does. */
1756
+ reason: UpscalingReason | null;
1757
+ /** Whether this plugin has upscaling on (`set`); a test may preset it. */
1758
+ enabled: boolean;
1759
+ /** The factor the player would request; `null` or at most 1: the video needs no upscaling. */
1760
+ factor: number | null;
1761
+ /** Whether the plugin made the player check the GPU (`state`, `set(true)`). */
1762
+ checked: boolean;
1763
+ /** GPU rendering is still starting: a `rendering` reason then lets `set(true)` keep the request. */
1764
+ renderingStarting: boolean;
1765
+ };
1706
1766
  /** What `player.chapters()` lists while media is ready. */
1707
1767
  chapters: {
1708
1768
  /** The chapter title, `null` without one. */
@@ -268,7 +268,8 @@ 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}, {@link TestHost.rendering}.
271
+ * {@link TestHost.tools}, {@link TestHost.rendering},
272
+ * {@link TestHost.upscaling}.
272
273
  * - **Assert** on records: {@link TestHost.osd}, {@link TestHost.calls},
273
274
  * {@link TestHost.sent}, {@link TestHost.conflicts},
274
275
  * {@link TestHost.layout}, {@link TestHost.menus}, and many more.
@@ -1622,6 +1623,60 @@ export class TestHost {
1622
1623
  gpu: { available: true, reason: null },
1623
1624
  restarts: 0,
1624
1625
  };
1626
+ /**
1627
+ * The GPU's video upscaler behind `plugin.video.upscaling` (player
1628
+ * 0.5.16), answered as the player would:
1629
+ *
1630
+ * - `state()` and `set(true)` set `checked` (the player's one GPU check; a
1631
+ * plugin that should not wake the GPU while it starts can assert it is
1632
+ * still `false`) and report `available` from `reason` (`null`: it
1633
+ * works), this plugin's `enabled`, and `requested` with `factor` while
1634
+ * enabled, available and `factor` is above 1.
1635
+ * - `set(true)` refuses with `unsupported` and the reason's message while
1636
+ * `reason` is set, unless its code is `decoding`, or `rendering` with
1637
+ * `renderingStarting` (GPU rendering still starting): those can pass
1638
+ * during the run, so the player keeps the request. Nothing changes when
1639
+ * it refuses.
1640
+ * - `set(false)` always succeeds and does not check the GPU: before any
1641
+ * check it reports `available: false` with the reason `unchecked`.
1642
+ *
1643
+ * Inject other answers with `host.handle("video.upscaling.set", …)`. The
1644
+ * defaults are a player where upscaling works and a 1080p video shown
1645
+ * full screen on a 4K display (`factor` 2), whatever `systemInfo` says.
1646
+ *
1647
+ * @example Test the menu item on a computer without an RTX GPU
1648
+ * The plugin's upscaling item is greyed out with the player's reason after
1649
+ * the user first picks it. Starting the plugin checks nothing (`checked`
1650
+ * stays false, so no GPU wakes up); the first pick reads the state, and
1651
+ * `set(true)` would reject with `unsupported`, as in the player.
1652
+ * ```ts
1653
+ * import assert from "node:assert/strict";
1654
+ * import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
1655
+ * import activate from "../main.ts";
1656
+ *
1657
+ * const host = new TestHost();
1658
+ * host.upscaling.reason = { code: "no-gpu", message: "no NVIDIA GPU" };
1659
+ * const { stop } = await startPlugin(activate, {
1660
+ * directory: new URL("..", import.meta.url),
1661
+ * host,
1662
+ * });
1663
+ * assert.equal(host.upscaling.checked, false); // starting woke no GPU
1664
+ * const item = host.command("upscaling");
1665
+ * await item.run(); // the first pick checks the GPU
1666
+ * assert.equal(item.state.enabled, false); // greyed out
1667
+ * assert.equal(item.state.reason, "no NVIDIA GPU"); // the player's message
1668
+ * await stop();
1669
+ * ```
1670
+ *
1671
+ * @since 1.5.37
1672
+ */
1673
+ upscaling = {
1674
+ reason: null,
1675
+ enabled: false,
1676
+ factor: 2,
1677
+ checked: false,
1678
+ renderingStarting: false,
1679
+ };
1625
1680
  /** What `player.chapters()` lists while media is ready. */
1626
1681
  chapters = [];
1627
1682
  #overrides = new Map();
@@ -3252,6 +3307,27 @@ export class TestHost {
3252
3307
  revision: stamp.revision + 1,
3253
3308
  };
3254
3309
  }
3310
+ /** `video.upscaling.state` from {@link TestHost.upscaling}. */
3311
+ #upscalingState(check = true) {
3312
+ if (check)
3313
+ this.upscaling.checked = true;
3314
+ const { enabled, factor } = this.upscaling;
3315
+ // `set(false)` before any check: the player answers without checking.
3316
+ const reason = this.upscaling.checked
3317
+ ? this.upscaling.reason
3318
+ : {
3319
+ code: "unchecked",
3320
+ message: "the player has not checked the GPU yet; state() or set(true) checks it",
3321
+ };
3322
+ const requested = enabled && reason === null && factor !== null && factor > 1;
3323
+ return {
3324
+ available: reason === null,
3325
+ reason: reason ? { ...reason } : null,
3326
+ enabled,
3327
+ requested,
3328
+ factor: requested ? factor : null,
3329
+ };
3330
+ }
3255
3331
  /** `rendering.state` from {@link TestHost.rendering} and `videoRender.gpu`. */
3256
3332
  #renderingState() {
3257
3333
  const { reason, preference, startedWith, gpu } = this.rendering;
@@ -3640,6 +3716,26 @@ export class TestHost {
3640
3716
  this.rendering.restarts++;
3641
3717
  return null;
3642
3718
  },
3719
+ // As the player 0.5.16 (see TestHost.upscaling for the model).
3720
+ "video.upscaling.state": () => this.#upscalingState(),
3721
+ "video.upscaling.set": (p) => {
3722
+ if (typeof p.enabled !== "boolean")
3723
+ throw new HostError("invalid", "enabled must be a boolean");
3724
+ const { reason, renderingStarting } = this.upscaling;
3725
+ // Off: always, without the GPU check.
3726
+ if (!p.enabled) {
3727
+ this.upscaling.enabled = false;
3728
+ return this.#upscalingState(false);
3729
+ }
3730
+ const passes = reason?.code === "decoding" ||
3731
+ (reason?.code === "rendering" && renderingStarting);
3732
+ if (reason && !passes) {
3733
+ this.upscaling.checked = true;
3734
+ throw new HostError("unsupported", `video upscaling cannot work here: ${reason.message}`);
3735
+ }
3736
+ this.upscaling.enabled = true;
3737
+ return this.#upscalingState();
3738
+ },
3643
3739
  "clipboard.readText": () => this.clipboard.text,
3644
3740
  "clipboard.writeText": (p) => {
3645
3741
  if (typeof p.text !== "string")
@@ -213,10 +213,11 @@ export interface Scope extends Disposable {
213
213
  /** Playback state and controls (`playback.read`, `events`, `playback.control`). */
214
214
  readonly player: Player;
215
215
  /**
216
- * Video processing: this plugin's filter chain, the display layer and the
217
- * GPU render options. All three need `processing.video` and
218
- * `playback.read`; the filter chain also needs `events` (without it the
219
- * SDK never sees the media become ready and the chain stays `waiting`).
216
+ * Video processing: this plugin's filter chain, the display layer, the
217
+ * GPU render options and the GPU's video upscaler. The first three need
218
+ * `processing.video` and `playback.read`; the filter chain also needs
219
+ * `events` (without it the SDK never sees the media become ready and the
220
+ * chain stays `waiting`). The upscaler needs `video.upscaling` only.
220
221
  */
221
222
  readonly video: {
222
223
  /**
@@ -249,6 +250,12 @@ export interface Scope extends Disposable {
249
250
  * while the first handle is not disposed.
250
251
  */
251
252
  render(): RenderOptions;
253
+ /**
254
+ * The GPU's video upscaler (NVIDIA RTX Video Super Resolution on
255
+ * Windows; needs `video.upscaling`, player 0.5.16): whether it works
256
+ * here and why not, and turning it on or off for this plugin.
257
+ */
258
+ readonly upscaling: VideoUpscaling;
252
259
  };
253
260
  /**
254
261
  * Audio processing: this plugin's filter chain and delay
@@ -6439,6 +6446,270 @@ export interface RenderOptions extends Disposable {
6439
6446
  */
6440
6447
  onStatus(listener: (status: FilterStatus) => void): Disposable;
6441
6448
  }
6449
+ /**
6450
+ * Why the GPU's video upscaler does not work, as a stable kebab-case code.
6451
+ * A newer player may add codes: compare with the ones you handle and show
6452
+ * {@link UpscalingReason.message} for the rest. The codes of player 0.5.16,
6453
+ * in the order the player checks them (the first that holds is reported):
6454
+ *
6455
+ * | Code | Meaning |
6456
+ * | --- | --- |
6457
+ * | `platform` | The player has no video upscaler on this system (macOS, Linux). |
6458
+ * | `no-gpu` | No GPU that can upscale video (Windows: no NVIDIA GPU). |
6459
+ * | `driver` | The graphics driver is too old (Windows: an NVIDIA driver before 530). |
6460
+ * | `gpu-unsupported` | The driver does not offer upscaling on this GPU (Windows: not an RTX GPU), or the check failed. |
6461
+ * | `rendering` | The player renders in software, or GPU rendering is still starting; upscaling needs GPU rendering ({@link Rendering}). |
6462
+ * | `adapter` | GPU rendering runs on another GPU than the one that can upscale (a laptop with two GPUs; Windows: choose the high-performance GPU for Vestra in Settings ▸ System ▸ Display ▸ Graphics). |
6463
+ * | `decoding` | The video is not decoded on the GPU that renders it: copy-back or software decoding, for example while a {@link VideoFilter} needs the frames in memory. |
6464
+ * | `unchecked` | The player has not checked the GPU yet. Only in the answer to `set(false)`, which never starts the check; `state()` and `set(true)` always check first. |
6465
+ *
6466
+ * `decoding` and `rendering` while GPU rendering is still starting can pass
6467
+ * during the run, so `set(true)` accepts them and keeps the request; every
6468
+ * other reason holds until the player restarts, and `set(true)` rejects it.
6469
+ * Turning upscaling off is always accepted.
6470
+ *
6471
+ * @example Advise the user by reason
6472
+ * The plugin shows its own advice for the reasons the user can act on and
6473
+ * the player's message for the rest. `reason.code` is looked up among the
6474
+ * codes the plugin knows; an unknown code from a newer player falls
6475
+ * through to the message, which names the GPU and what to do.
6476
+ * ```ts
6477
+ * const { reason } = await plugin.video.upscaling.state();
6478
+ * // Advice for the codes this plugin knows; any other code shows the message.
6479
+ * const advice: Partial<Record<UpscalingReasonCode, string>> = {
6480
+ * driver: "Update the NVIDIA driver to 530 or newer.",
6481
+ * rendering: "Turn on Video ▸ GPU Rendering (Experimental) and restart.",
6482
+ * };
6483
+ * if (reason) console.log(advice[reason.code] ?? reason.message);
6484
+ * ```
6485
+ *
6486
+ * @player 0.5.16
6487
+ * @since 1.5.37
6488
+ * @category Processing
6489
+ */
6490
+ export type UpscalingReasonCode = "platform" | "no-gpu" | "driver" | "gpu-unsupported" | "rendering" | "adapter" | "decoding" | "unchecked" | (string & {});
6491
+ /**
6492
+ * Why the GPU's video upscaler does not work: a stable
6493
+ * {@link UpscalingReasonCode} to branch on and a sentence to show, the same
6494
+ * shape as {@link RenderingReason}.
6495
+ *
6496
+ * @example Grey out a menu item with the player's reason
6497
+ * The plugin disables its upscaling item and shows why. `message` is the
6498
+ * player's English text, naming the GPU and what the user can do (turn on
6499
+ * GPU rendering, choose the other GPU, update the driver); `code` is for
6500
+ * decisions.
6501
+ * ```ts
6502
+ * const { available, reason } = await plugin.video.upscaling.state();
6503
+ * if (!available && reason) console.log(`${reason.code}: ${reason.message}`);
6504
+ * ```
6505
+ *
6506
+ * @player 0.5.16
6507
+ * @since 1.5.37
6508
+ * @category Processing
6509
+ */
6510
+ export interface UpscalingReason {
6511
+ /** The reason as a stable kebab-case code; branch on this. */
6512
+ readonly code: UpscalingReasonCode;
6513
+ /**
6514
+ * The reason as English text for people, for example with the GPU's name
6515
+ * and what to change; never branch on it.
6516
+ */
6517
+ readonly message: string;
6518
+ }
6519
+ /**
6520
+ * What {@link VideoUpscaling.state} and {@link VideoUpscaling.set} report:
6521
+ * whether the upscaler works now and why not, whether this plugin turned
6522
+ * it on, and whether the player asked the GPU to upscale.
6523
+ *
6524
+ * @remarks
6525
+ * `requested` is as far as the player can see. The player puts the
6526
+ * upscaler last in the video filter chain, scaling the video to the area it
6527
+ * is shown in; the driver gives no sign that it upscales. On Windows the
6528
+ * user must also turn on Super Resolution in the NVIDIA app (System ▸ Video
6529
+ * ▸ RTX Video Enhancement), which no program can read: say so next to the
6530
+ * switch.
6531
+ *
6532
+ * @example Label the item with what happens now
6533
+ * The plugin's menu item says whether upscaling is on and whether the
6534
+ * video is being upscaled. `enabled` is this plugin's own request;
6535
+ * `requested` is true while the player has the upscaler in the filter
6536
+ * chain, which needs a video shown larger than its own size (`factor`
6537
+ * above 1).
6538
+ * ```ts
6539
+ * const state = await plugin.video.upscaling.state();
6540
+ * // `enabled`: this plugin's request; `requested`: in the filter chain now.
6541
+ * const label = !state.enabled
6542
+ * ? "AI Upscaling: Off"
6543
+ * : state.requested
6544
+ * ? `AI Upscaling: On (×${state.factor?.toFixed(2)})`
6545
+ * : "AI Upscaling: On (not needed for this video)";
6546
+ * console.log(label);
6547
+ * ```
6548
+ *
6549
+ * @player 0.5.16
6550
+ * @since 1.5.37
6551
+ * @category Processing
6552
+ */
6553
+ export interface UpscalingState {
6554
+ /** Whether the upscaler works now; `false` with {@link UpscalingState.reason}. */
6555
+ readonly available: boolean;
6556
+ /** Why it does not work now; `null` while it does. */
6557
+ readonly reason: UpscalingReason | null;
6558
+ /** This plugin asked for upscaling ({@link VideoUpscaling.set}) and has not turned it off. */
6559
+ readonly enabled: boolean;
6560
+ /**
6561
+ * The player has the upscaler in the video filter chain now, for this
6562
+ * plugin or another one: upscaling is enabled and available and the
6563
+ * video is shown larger than its own size. Not a sign that the GPU
6564
+ * upscales (see the remarks).
6565
+ */
6566
+ readonly requested: boolean;
6567
+ /**
6568
+ * The scale factor the upscaler was asked for (the area the video is
6569
+ * shown in over the video's size, in hundredths, above 1); `null` while
6570
+ * nothing is requested.
6571
+ */
6572
+ readonly factor: number | null;
6573
+ }
6574
+ /**
6575
+ * `plugin.video.upscaling`: the GPU's video upscaler, NVIDIA RTX Video
6576
+ * Super Resolution on Windows (`video.upscaling`). Read whether it works
6577
+ * here and why not; turn it on or off for this plugin.
6578
+ *
6579
+ * @remarks
6580
+ * The player checks the GPU once, when a plugin first asks, and keeps the
6581
+ * answer: the check wakes a sleeping discrete GPU, so call
6582
+ * {@link VideoUpscaling.state} when the plugin first needs the answer (the
6583
+ * first time the user picks its upscaling item), not while the plugin
6584
+ * starts, and do not poll it. `set(false)` never starts the check. It works
6585
+ * only with GPU rendering, on the GPU the upscaler runs on, with the video decoded on that GPU; {@link UpscalingReasonCode}
6586
+ * lists what can stand in the way.
6587
+ *
6588
+ * While some plugin has it on and it works, the player puts the upscaler
6589
+ * last in the video filter chain and scales the video to the area it is
6590
+ * shown in, following window and video size changes (a resize waits until
6591
+ * the size holds). A video already shown at its own size or smaller is not
6592
+ * upscaled. The upscaler stays while any plugin has it on; it goes when the
6593
+ * last one turns it off or stops: the player revokes a stopped plugin's
6594
+ * request.
6595
+ *
6596
+ * **Windows:** the user must turn on Super Resolution in the NVIDIA app
6597
+ * (System ▸ Video ▸ RTX Video Enhancement); no program can read that
6598
+ * switch, and the driver gives no sign that it upscales
6599
+ * ({@link UpscalingState.requested} is as far as the player can see). It
6600
+ * uses the NVIDIA GPU, so power use and heat go up, and on a laptop with
6601
+ * two GPUs the whole player must run on the NVIDIA one.
6602
+ * **macOS, Linux:** `available` is `false` with `platform`.
6603
+ *
6604
+ * Players before 0.5.16 do not know `video.upscaling` and do not load a
6605
+ * plugin that declares it: declare `"minPlayerVersion": "0.5.16"`.
6606
+ *
6607
+ * @example Offer AI upscaling as a menu item
6608
+ * A Video menu item turns upscaling on and off; once the player said it
6609
+ * cannot work here, the item is greyed out with the player's reason. The
6610
+ * state is read the first time the user picks the item (that runs the
6611
+ * player's GPU check, so nothing wakes the GPU while the plugin starts) and
6612
+ * again after each pick; `set` resolves with the new state, or rejects with
6613
+ * `unsupported` where upscaling cannot work in this run.
6614
+ * ```ts
6615
+ * let state: UpscalingState | null = null; // unknown until first picked
6616
+ * plugin.commands.add({
6617
+ * id: "upscaling",
6618
+ * path: ["@video", "AI Upscaling"],
6619
+ * checked: () => state?.enabled ?? false,
6620
+ * // Greyed only once the player said it cannot work (`decoding` can pass).
6621
+ * enabled: () => state === null || state.available || state.reason?.code === "decoding",
6622
+ * reason: () => state?.reason?.message ?? null, // shown while disabled
6623
+ * run: async () => {
6624
+ * state ??= await plugin.video.upscaling.state(); // the first pick checks the GPU
6625
+ * try {
6626
+ * state = await plugin.video.upscaling.set(!(state?.enabled ?? false));
6627
+ * } catch (error) {
6628
+ * // Cannot work in this run (no RTX GPU, software rendering…).
6629
+ * if ((error as PluginError).code !== "unsupported") throw error;
6630
+ * state = await plugin.video.upscaling.state();
6631
+ * }
6632
+ * },
6633
+ * });
6634
+ * ```
6635
+ *
6636
+ * @capability video.upscaling
6637
+ * @player 0.5.16; older players do not load a plugin that declares
6638
+ * `video.upscaling`.
6639
+ * @since 1.5.37
6640
+ * @category Processing
6641
+ */
6642
+ export interface VideoUpscaling {
6643
+ /**
6644
+ * Read whether the upscaler works now and why not, whether this plugin
6645
+ * turned it on and what the player asked the GPU for.
6646
+ *
6647
+ * @capability video.upscaling
6648
+ * @returns The state, once the player's GPU check ran (the first call
6649
+ * starts it and may take a moment).
6650
+ * @throws `capability` (rejects) The manifest does not declare
6651
+ * `video.upscaling`.
6652
+ *
6653
+ * @example Find out whether upscaling can work here
6654
+ * The plugin reads the state the first time the user picks its upscaling
6655
+ * item and keeps it for the item. The first call makes the player check the GPU (which
6656
+ * wakes a sleeping discrete GPU) and later calls reuse the answer; the
6657
+ * rest (GPU rendering, the GPU it runs on, the decoding) is read at
6658
+ * each call.
6659
+ * ```ts
6660
+ * const { available, reason } = await plugin.video.upscaling.state();
6661
+ * if (!available) console.log(reason?.message);
6662
+ * ```
6663
+ *
6664
+ * @since 1.5.37
6665
+ */
6666
+ state(): Promise<UpscalingState>;
6667
+ /**
6668
+ * Turn upscaling on or off for this plugin.
6669
+ *
6670
+ * @remarks
6671
+ * On stays this plugin's request until it turns it off or stops; the
6672
+ * player upscales whenever the request can take effect (a `decoding`
6673
+ * reason can pass with the next file). Off takes the upscaler out at
6674
+ * once unless another plugin has it on. Upscaling is a request of the
6675
+ * whole plugin, not of a {@link Scope}: disposing a scope does not turn
6676
+ * it off.
6677
+ *
6678
+ * @capability video.upscaling
6679
+ * @param enabled - `true` to ask for upscaling, `false` to withdraw the
6680
+ * request.
6681
+ * @returns The new state.
6682
+ * @throws `capability` (rejects) The manifest does not declare
6683
+ * `video.upscaling`.
6684
+ * @throws `invalid` (rejects) `enabled` is not a boolean.
6685
+ * @throws `unsupported` (rejects) `true` where upscaling cannot work in
6686
+ * this run: every {@link UpscalingReasonCode} except `decoding`, and
6687
+ * except `rendering` while GPU rendering is still starting (those two
6688
+ * are accepted and the request is kept until they pass); `message` is
6689
+ * the reason. Nothing changes. `false` is always accepted and answered
6690
+ * at once without the GPU check (before any check, `available` is
6691
+ * `false` with `unchecked`).
6692
+ *
6693
+ * @example Turn upscaling on and explain what is needed
6694
+ * The plugin turns upscaling on from its settings and tells the user what
6695
+ * else to switch on. `set(true)` records the request and resolves with the
6696
+ * new state; where upscaling cannot work in this run it rejects with
6697
+ * `unsupported` and the player's reason instead.
6698
+ * ```ts
6699
+ * try {
6700
+ * const state = await plugin.video.upscaling.set(true);
6701
+ * // The driver's own switch cannot be read: remind the user once.
6702
+ * if (state.enabled) await plugin.osd.show("Turn on Super Resolution in the NVIDIA app");
6703
+ * } catch (error) {
6704
+ * if ((error as PluginError).code !== "unsupported") throw error;
6705
+ * await plugin.osd.show("AI upscaling: {}", [(error as PluginError).message]);
6706
+ * }
6707
+ * ```
6708
+ *
6709
+ * @since 1.5.37
6710
+ */
6711
+ set(enabled: boolean): Promise<UpscalingState>;
6712
+ }
6442
6713
  /**
6443
6714
  * A subtitle track of the current source.
6444
6715
  *
package/dist/wire.d.ts CHANGED
@@ -336,6 +336,15 @@ export interface WireRenderingState {
336
336
  reason: WireRenderingReason | null;
337
337
  };
338
338
  }
339
+ /** `video.upscaling.state` and `video.upscaling.set` (player 0.5.16). */
340
+ export interface WireUpscalingState {
341
+ available: boolean;
342
+ /** The same `{ code, message }` shape as a rendering reason. */
343
+ reason: WireRenderingReason | null;
344
+ enabled: boolean;
345
+ requested: boolean;
346
+ factor: number | null;
347
+ }
339
348
  /**
340
349
  * Template arguments on the wire (`osd.show`, `osd.panel`, `dialogs.confirm`,
341
350
  * a text's `args`): data, or text values (player 0.4.3).
@@ -468,6 +477,9 @@ export declare function createWire(transport: Transport): {
468
477
  state: "applied" | "waiting";
469
478
  }>;
470
479
  renderRelease: () => Promise<void>;
480
+ /** The GPU's video upscaler (`plugin.video.upscaling`, player 0.5.16). */
481
+ upscalingState: () => Promise<WireUpscalingState>;
482
+ upscalingSet: (enabled: boolean) => Promise<WireUpscalingState>;
471
483
  };
472
484
  /** Mark layers on the seek bar (`plugin.timeline`). */
473
485
  timeline: {
package/dist/wire.js CHANGED
@@ -138,6 +138,11 @@ export function createWire(transport) {
138
138
  /** The GPU renderer's options (`plugin.video.render()`). */
139
139
  renderSet: (values) => transport.request("video.render.set", { values }),
140
140
  renderRelease: () => transport.request("video.render.release"),
141
+ /** The GPU's video upscaler (`plugin.video.upscaling`, player 0.5.16). */
142
+ upscalingState: () => transport.request("video.upscaling.state"),
143
+ upscalingSet: (enabled) => transport.request("video.upscaling.set", {
144
+ enabled,
145
+ }),
141
146
  },
142
147
  /** Mark layers on the seek bar (`plugin.timeline`). */
143
148
  timeline: {
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.35: full reference
1
+ # @vestra-player/sdk 1.5.37: 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
 
@@ -743,6 +743,14 @@ plugin.video.render() returns the RenderOptions of the GPU renderer: HDR to SDR
743
743
  - **Only the GPU renderer has them.** Under software rendering every `set` fails with `unsupported`; see Reference: platforms for where the player renders on the GPU. Treat `unsupported` as "hide these settings", not as an error. To know before creating the handle, read rendering from `plugin.app.system()` (player 0.5.12, no capability): `"gpu"` or `"software"`.
744
744
  - A shader that does not compile is reported later: the status first becomes `applied`, then `failed` with code `failed` and the compiler's first error line once mpv first uses the shader.
745
745
 
746
+ #### The GPU's video upscaler
747
+
748
+ plugin.video.upscaling (capability `video.upscaling`, player 0.5.16) turns on the GPU's AI upscaler, NVIDIA RTX Video Super Resolution on Windows. It is not a handle: `set(true)` is the plugin's request, and the player keeps the upscaler last in the filter chain, after every plugin's chain, while any plugin has it on and it can work, scaled to the area the video is shown in. Like render options it acts at display time only.
749
+
750
+ - **Ask when needed.** The player checks the GPU at the first `state()` or `set(true)` and keeps the answer; the check wakes a sleeping discrete GPU, so ask on the first pick of the item, not while the plugin starts. `set(false)` never checks.
751
+ - **It needs GPU rendering on the upscaler's GPU, with the video decoded there.** A filter chain that needs the frames in memory switches decoding to copy-back, and the upscaler waits (`decoding`) until it is gone.
752
+ - **No sign that it runs.** `requested` says the player asked the driver; on Windows the user must also turn on Super Resolution in the NVIDIA app, which no program can read.
753
+
746
754
  ## Concept: Windows, panels, settings pages, dialogs and the OSD
747
755
 
748
756
  A plugin can show its own user interface in five places. Three of them are drawn by the plugin's own Slint files: **tool windows**, **panels** and **settings pages**. Two are drawn by the player or the system from plain values: **dialogs** and the **on-screen display (OSD)**. This page explains what each one is, how to choose, how each one lives and dies, how Slint and TypeScript talk to each other, and what to watch out for.
@@ -2690,6 +2698,7 @@ The table lists the API whose documentation names a player version, newest last:
2690
2698
  | 0.5.10 | CommandButton, CommandOptions.button |
2691
2699
  | 0.5.12 | SystemInfo.rendering |
2692
2700
  | 0.5.13 | SystemInfo.renderingReason, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState, Rendering |
2701
+ | 0.5.16 | UpscalingReasonCode, UpscalingReason, UpscalingState, VideoUpscaling |
2693
2702
 
2694
2703
  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).
2695
2704
 
@@ -2727,6 +2736,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2727
2736
  | `ui.theme` | Appearance.set, Appearance.setLanguage | — | Change the player's palette | |
2728
2737
  | `secrets` | Secrets.get, Secrets.set, Secrets.delete | — | Store sign-in details in the system keychain | yes |
2729
2738
  | `rendering.manage` | Rendering.setPreference, Rendering.restartPlayer | — | Choose GPU or software rendering and restart the player | yes |
2739
+ | `video.upscaling` | VideoUpscaling.state, VideoUpscaling.set | — | Upscale the video with the GPU's video upscaler | |
2730
2740
 
2731
2741
  **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:
2732
2742
 
@@ -3175,6 +3185,7 @@ Every command the player (`host`) and its built-in plugins register when the pla
3175
3185
  | Video ▸ Saturation +1% | `U` | `vestra.video/video.saturation.up` |
3176
3186
  | Video ▸ Hue -1% | `I` | `vestra.video/video.hue.down` |
3177
3187
  | Video ▸ Hue +1% | `O` | `vestra.video/video.hue.up` |
3188
+ | Video ▸ GPU Rendering (Experimental) | | `vestra.video/video.gpu-rendering` |
3178
3189
  | Audio ▸ Volume + | `ArrowUp`, `Mouse:WheelUp` | `host/volume-up` |
3179
3190
  | Audio ▸ Volume − | `ArrowDown`, `Mouse:WheelDown` | `host/volume-down` |
3180
3191
  | Audio ▸ Mute | `M` | `host/mute` |
@@ -3389,6 +3400,7 @@ A plugin uses the same API on macOS and Windows; the player does the system-spec
3389
3400
  | Rendering | On the GPU (OpenGL), with hardware decoding. | In software, with hardware decoding copied back (`d3d11va-copy` and similar). |
3390
3401
  | 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`. |
3391
3402
  | Render options (RenderOptions) | Available; `peakDetect` has no effect. | `unsupported` (software rendering). |
3403
+ | plugin.video.upscaling (player 0.5.16) | `available` is `false` (`platform`); `set(true)` fails with `unsupported`. | NVIDIA RTX Video Super Resolution: an RTX GPU with driver 530 or newer, GPU rendering on that GPU, the video decoded on it (`d3d11va`); the user must also turn on Super Resolution in the NVIDIA app, which no program can read. |
3392
3404
  | Bindings | `Ctrl` also matches ⌘; `Cmd` is ⌘. | `Cmd` is the Windows logo key; `Cmd+Q` does not quit. |
3393
3405
  | Keys taken first, standard close keys | See Keys the system takes first. | See Keys the system takes first. |
3394
3406
  | Interface font | The system font; CJK falls back per character to the system's fonts (PingFang, Hiragino). | The system font; CJK falls back per character to Windows' CJK fonts in the interface language's order, then the bundled Noto Sans CJK SC (the chain). |
@@ -3867,7 +3879,7 @@ interface Scope extends Disposable {
3867
3879
  /** Playback state and controls (`playback.read`, `events`, `playback.control`). */
3868
3880
  readonly player: Player;
3869
3881
  /**
3870
- * Video processing: this plugin's filter chain, the display layer and the GPU render options. All three need `processing.video` and `playback.read`; the filter chain also needs `events` (without it the SDK never sees the media become ready and the chain stays `waiting`).
3882
+ * Video processing: this plugin's filter chain, the display layer, the GPU render options and the GPU's video upscaler. The first three need `processing.video` and `playback.read`; the filter chain also needs `events` (without it the SDK never sees the media become ready and the chain stays `waiting`). The upscaler needs `video.upscaling` only.
3871
3883
  */
3872
3884
  readonly video: {
3873
3885
  /**
@@ -3888,6 +3900,10 @@ interface Scope extends Disposable {
3888
3900
  * @throws Error without a code (throws) A second call in the same plugin while the first handle is not disposed.
3889
3901
  */
3890
3902
  render(): RenderOptions;
3903
+ /**
3904
+ * The GPU's video upscaler (NVIDIA RTX Video Super Resolution on Windows; needs `video.upscaling`, player 0.5.16): whether it works here and why not, and turning it on or off for this plugin.
3905
+ */
3906
+ readonly upscaling: VideoUpscaling;
3891
3907
  };
3892
3908
  /** Audio processing: this plugin's filter chain and delay (`processing.audio`, `playback.read` and `events`, as for VideoFilter). */
3893
3909
  readonly audio: {
@@ -9404,6 +9420,251 @@ render.onStatus((s) => {
9404
9420
  render.set({ toneMapping: "bt.2390", targetPeak: 250 }); // nits
9405
9421
  ```
9406
9422
 
9423
+ ### UpscalingReasonCode
9424
+
9425
+ Why the GPU's video upscaler does not work, as a stable kebab-case code.
9426
+ A newer player may add codes: compare with the ones you handle and show
9427
+ UpscalingReason.message for the rest. The codes of player 0.5.16,
9428
+ in the order the player checks them (the first that holds is reported):
9429
+
9430
+ | Code | Meaning |
9431
+ | --- | --- |
9432
+ | `platform` | The player has no video upscaler on this system (macOS, Linux). |
9433
+ | `no-gpu` | No GPU that can upscale video (Windows: no NVIDIA GPU). |
9434
+ | `driver` | The graphics driver is too old (Windows: an NVIDIA driver before 530). |
9435
+ | `gpu-unsupported` | The driver does not offer upscaling on this GPU (Windows: not an RTX GPU), or the check failed. |
9436
+ | `rendering` | The player renders in software, or GPU rendering is still starting; upscaling needs GPU rendering (Rendering). |
9437
+ | `adapter` | GPU rendering runs on another GPU than the one that can upscale (a laptop with two GPUs; Windows: choose the high-performance GPU for Vestra in Settings ▸ System ▸ Display ▸ Graphics). |
9438
+ | `decoding` | The video is not decoded on the GPU that renders it: copy-back or software decoding, for example while a VideoFilter needs the frames in memory. |
9439
+ | `unchecked` | The player has not checked the GPU yet. Only in the answer to `set(false)`, which never starts the check; `state()` and `set(true)` always check first. |
9440
+
9441
+ `decoding` and `rendering` while GPU rendering is still starting can pass
9442
+ during the run, so `set(true)` accepts them and keeps the request; every
9443
+ other reason holds until the player restarts, and `set(true)` rejects it.
9444
+ Turning upscaling off is always accepted.
9445
+
9446
+ - @player 0.5.16
9447
+
9448
+ ```ts
9449
+ type UpscalingReasonCode = "platform" | "no-gpu" | "driver" | "gpu-unsupported" | "rendering" | "adapter" | "decoding" | "unchecked" | (string & {});
9450
+ ```
9451
+
9452
+ Example: Advise the user by reason
9453
+
9454
+ The plugin shows its own advice for the reasons the user can act on and
9455
+ the player's message for the rest. `reason.code` is looked up among the
9456
+ codes the plugin knows; an unknown code from a newer player falls
9457
+ through to the message, which names the GPU and what to do.
9458
+ ```ts
9459
+ const { reason } = await plugin.video.upscaling.state();
9460
+ // Advice for the codes this plugin knows; any other code shows the message.
9461
+ const advice: Partial<Record<UpscalingReasonCode, string>> = {
9462
+ driver: "Update the NVIDIA driver to 530 or newer.",
9463
+ rendering: "Turn on Video ▸ GPU Rendering (Experimental) and restart.",
9464
+ };
9465
+ if (reason) console.log(advice[reason.code] ?? reason.message);
9466
+ ```
9467
+
9468
+ ### UpscalingReason
9469
+
9470
+ Why the GPU's video upscaler does not work: a stable
9471
+ UpscalingReasonCode to branch on and a sentence to show, the same
9472
+ shape as RenderingReason.
9473
+
9474
+ - @player 0.5.16
9475
+
9476
+ ```ts
9477
+ interface UpscalingReason {
9478
+ /** The reason as a stable kebab-case code; branch on this. */
9479
+ readonly code: UpscalingReasonCode;
9480
+ /** The reason as English text for people, for example with the GPU's name and what to change; never branch on it. */
9481
+ readonly message: string;
9482
+ }
9483
+ ```
9484
+
9485
+ Example: Grey out a menu item with the player's reason
9486
+
9487
+ The plugin disables its upscaling item and shows why. `message` is the
9488
+ player's English text, naming the GPU and what the user can do (turn on
9489
+ GPU rendering, choose the other GPU, update the driver); `code` is for
9490
+ decisions.
9491
+ ```ts
9492
+ const { available, reason } = await plugin.video.upscaling.state();
9493
+ if (!available && reason) console.log(`${reason.code}: ${reason.message}`);
9494
+ ```
9495
+
9496
+ ### UpscalingState
9497
+
9498
+ What VideoUpscaling.state and VideoUpscaling.set report:
9499
+ whether the upscaler works now and why not, whether this plugin turned
9500
+ it on, and whether the player asked the GPU to upscale.
9501
+
9502
+ `requested` is as far as the player can see. The player puts the
9503
+ upscaler last in the video filter chain, scaling the video to the area it
9504
+ is shown in; the driver gives no sign that it upscales. On Windows the
9505
+ user must also turn on Super Resolution in the NVIDIA app (System ▸ Video
9506
+ ▸ RTX Video Enhancement), which no program can read: say so next to the
9507
+ switch.
9508
+
9509
+ - @player 0.5.16
9510
+
9511
+ ```ts
9512
+ interface UpscalingState {
9513
+ /** Whether the upscaler works now; `false` with UpscalingState.reason. */
9514
+ readonly available: boolean;
9515
+ /** Why it does not work now; `null` while it does. */
9516
+ readonly reason: UpscalingReason | null;
9517
+ /** This plugin asked for upscaling (VideoUpscaling.set) and has not turned it off. */
9518
+ readonly enabled: boolean;
9519
+ /**
9520
+ * The player has the upscaler in the video filter chain now, for this plugin or another one: upscaling is enabled and available and the video is shown larger than its own size. Not a sign that the GPU upscales (see the remarks).
9521
+ */
9522
+ readonly requested: boolean;
9523
+ /**
9524
+ * The scale factor the upscaler was asked for (the area the video is shown in over the video's size, in hundredths, above 1); `null` while nothing is requested.
9525
+ */
9526
+ readonly factor: number | null;
9527
+ }
9528
+ ```
9529
+
9530
+ Example: Label the item with what happens now
9531
+
9532
+ The plugin's menu item says whether upscaling is on and whether the
9533
+ video is being upscaled. `enabled` is this plugin's own request;
9534
+ `requested` is true while the player has the upscaler in the filter
9535
+ chain, which needs a video shown larger than its own size (`factor`
9536
+ above 1).
9537
+ ```ts
9538
+ const state = await plugin.video.upscaling.state();
9539
+ // `enabled`: this plugin's request; `requested`: in the filter chain now.
9540
+ const label = !state.enabled
9541
+ ? "AI Upscaling: Off"
9542
+ : state.requested
9543
+ ? `AI Upscaling: On (×${state.factor?.toFixed(2)})`
9544
+ : "AI Upscaling: On (not needed for this video)";
9545
+ console.log(label);
9546
+ ```
9547
+
9548
+ ### VideoUpscaling
9549
+
9550
+ `plugin.video.upscaling`: the GPU's video upscaler, NVIDIA RTX Video
9551
+ Super Resolution on Windows (`video.upscaling`). Read whether it works
9552
+ here and why not; turn it on or off for this plugin.
9553
+
9554
+ The player checks the GPU once, when a plugin first asks, and keeps the
9555
+ answer: the check wakes a sleeping discrete GPU, so call
9556
+ VideoUpscaling.state when the plugin first needs the answer (the
9557
+ first time the user picks its upscaling item), not while the plugin
9558
+ starts, and do not poll it. `set(false)` never starts the check. It works
9559
+ only with GPU rendering, on the GPU the upscaler runs on, with the video decoded on that GPU; UpscalingReasonCode
9560
+ lists what can stand in the way.
9561
+
9562
+ While some plugin has it on and it works, the player puts the upscaler
9563
+ last in the video filter chain and scales the video to the area it is
9564
+ shown in, following window and video size changes (a resize waits until
9565
+ the size holds). A video already shown at its own size or smaller is not
9566
+ upscaled. The upscaler stays while any plugin has it on; it goes when the
9567
+ last one turns it off or stops: the player revokes a stopped plugin's
9568
+ request.
9569
+
9570
+ **Windows:** the user must turn on Super Resolution in the NVIDIA app
9571
+ (System ▸ Video ▸ RTX Video Enhancement); no program can read that
9572
+ switch, and the driver gives no sign that it upscales
9573
+ (UpscalingState.requested is as far as the player can see). It
9574
+ uses the NVIDIA GPU, so power use and heat go up, and on a laptop with
9575
+ two GPUs the whole player must run on the NVIDIA one.
9576
+ **macOS, Linux:** `available` is `false` with `platform`.
9577
+
9578
+ Players before 0.5.16 do not know `video.upscaling` and do not load a
9579
+ plugin that declares it: declare `"minPlayerVersion": "0.5.16"`.
9580
+
9581
+ - @capability video.upscaling
9582
+ - @player 0.5.16; older players do not load a plugin that declares `video.upscaling`.
9583
+
9584
+ ```ts
9585
+ interface VideoUpscaling {
9586
+ /**
9587
+ * Read whether the upscaler works now and why not, whether this plugin turned it on and what the player asked the GPU for.
9588
+ * @capability video.upscaling
9589
+ * @returns The state, once the player's GPU check ran (the first call starts it and may take a moment).
9590
+ * @throws capability (rejects) The manifest does not declare `video.upscaling`.
9591
+ * @example Find out whether upscaling can work here
9592
+ * The plugin reads the state the first time the user picks its upscaling
9593
+ * item and keeps it for the item. The first call makes the player check the GPU (which
9594
+ * wakes a sleeping discrete GPU) and later calls reuse the answer; the
9595
+ * rest (GPU rendering, the GPU it runs on, the decoding) is read at
9596
+ * each call.
9597
+ * ```ts
9598
+ * const { available, reason } = await plugin.video.upscaling.state();
9599
+ * if (!available) console.log(reason?.message);
9600
+ * ```
9601
+ */
9602
+ state(): Promise<UpscalingState>;
9603
+ /**
9604
+ * Turn upscaling on or off for this plugin.
9605
+ *
9606
+ * On stays this plugin's request until it turns it off or stops; the
9607
+ * player upscales whenever the request can take effect (a `decoding`
9608
+ * reason can pass with the next file). Off takes the upscaler out at
9609
+ * once unless another plugin has it on. Upscaling is a request of the
9610
+ * whole plugin, not of a Scope: disposing a scope does not turn
9611
+ * it off.
9612
+ * @capability video.upscaling
9613
+ * @param enabled - `true` to ask for upscaling, `false` to withdraw the request.
9614
+ * @returns The new state.
9615
+ * @throws capability (rejects) The manifest does not declare `video.upscaling`.
9616
+ * @throws invalid (rejects) `enabled` is not a boolean.
9617
+ * @throws unsupported (rejects) `true` where upscaling cannot work in this run: every UpscalingReasonCode except `decoding`, and except `rendering` while GPU rendering is still starting (those two are accepted and the request is kept until they pass); `message` is the reason. Nothing changes. `false` is always accepted and answered at once without the GPU check (before any check, `available` is `false` with `unchecked`).
9618
+ * @example Turn upscaling on and explain what is needed
9619
+ * The plugin turns upscaling on from its settings and tells the user what
9620
+ * else to switch on. `set(true)` records the request and resolves with the
9621
+ * new state; where upscaling cannot work in this run it rejects with
9622
+ * `unsupported` and the player's reason instead.
9623
+ * ```ts
9624
+ * try {
9625
+ * const state = await plugin.video.upscaling.set(true);
9626
+ * // The driver's own switch cannot be read: remind the user once.
9627
+ * if (state.enabled) await plugin.osd.show("Turn on Super Resolution in the NVIDIA app");
9628
+ * } catch (error) {
9629
+ * if ((error as PluginError).code !== "unsupported") throw error;
9630
+ * await plugin.osd.show("AI upscaling: {}", [(error as PluginError).message]);
9631
+ * }
9632
+ * ```
9633
+ */
9634
+ set(enabled: boolean): Promise<UpscalingState>;
9635
+ }
9636
+ ```
9637
+
9638
+ Example: Offer AI upscaling as a menu item
9639
+
9640
+ A Video menu item turns upscaling on and off; once the player said it
9641
+ cannot work here, the item is greyed out with the player's reason. The
9642
+ state is read the first time the user picks the item (that runs the
9643
+ player's GPU check, so nothing wakes the GPU while the plugin starts) and
9644
+ again after each pick; `set` resolves with the new state, or rejects with
9645
+ `unsupported` where upscaling cannot work in this run.
9646
+ ```ts
9647
+ let state: UpscalingState | null = null; // unknown until first picked
9648
+ plugin.commands.add({
9649
+ id: "upscaling",
9650
+ path: ["@video", "AI Upscaling"],
9651
+ checked: () => state?.enabled ?? false,
9652
+ // Greyed only once the player said it cannot work (`decoding` can pass).
9653
+ enabled: () => state === null || state.available || state.reason?.code === "decoding",
9654
+ reason: () => state?.reason?.message ?? null, // shown while disabled
9655
+ run: async () => {
9656
+ state ??= await plugin.video.upscaling.state(); // the first pick checks the GPU
9657
+ try {
9658
+ state = await plugin.video.upscaling.set(!(state?.enabled ?? false));
9659
+ } catch (error) {
9660
+ // Cannot work in this run (no RTX GPU, software rendering…).
9661
+ if ((error as PluginError).code !== "unsupported") throw error;
9662
+ state = await plugin.video.upscaling.state();
9663
+ }
9664
+ },
9665
+ });
9666
+ ```
9667
+
9407
9668
  ### SubtitleTrack
9408
9669
 
9409
9670
  A subtitle track of the current source.
@@ -12503,7 +12764,8 @@ Members fall into a few groups:
12503
12764
  TestHost.app, TestHost.systemInfo,
12504
12765
  TestHost.chapters, TestHost.audioDevices,
12505
12766
  TestHost.secrets, TestHost.clipboard,
12506
- TestHost.tools, TestHost.rendering.
12767
+ TestHost.tools, TestHost.rendering,
12768
+ TestHost.upscaling.
12507
12769
  - **Assert** on records: TestHost.osd, TestHost.calls,
12508
12770
  TestHost.sent, TestHost.conflicts,
12509
12771
  TestHost.layout, TestHost.menus, and many more.
@@ -13406,6 +13668,62 @@ class TestHost implements Transport {
13406
13668
  /** Accepted `restartPlayer` calls. */
13407
13669
  restarts: number;
13408
13670
  };
13671
+ /**
13672
+ * The GPU's video upscaler behind `plugin.video.upscaling` (player
13673
+ * 0.5.16), answered as the player would:
13674
+ *
13675
+ * - `state()` and `set(true)` set `checked` (the player's one GPU check; a
13676
+ * plugin that should not wake the GPU while it starts can assert it is
13677
+ * still `false`) and report `available` from `reason` (`null`: it
13678
+ * works), this plugin's `enabled`, and `requested` with `factor` while
13679
+ * enabled, available and `factor` is above 1.
13680
+ * - `set(true)` refuses with `unsupported` and the reason's message while
13681
+ * `reason` is set, unless its code is `decoding`, or `rendering` with
13682
+ * `renderingStarting` (GPU rendering still starting): those can pass
13683
+ * during the run, so the player keeps the request. Nothing changes when
13684
+ * it refuses.
13685
+ * - `set(false)` always succeeds and does not check the GPU: before any
13686
+ * check it reports `available: false` with the reason `unchecked`.
13687
+ *
13688
+ * Inject other answers with `host.handle("video.upscaling.set", …)`. The
13689
+ * defaults are a player where upscaling works and a 1080p video shown
13690
+ * full screen on a 4K display (`factor` 2), whatever `systemInfo` says.
13691
+ * @example Test the menu item on a computer without an RTX GPU
13692
+ * The plugin's upscaling item is greyed out with the player's reason after
13693
+ * the user first picks it. Starting the plugin checks nothing (`checked`
13694
+ * stays false, so no GPU wakes up); the first pick reads the state, and
13695
+ * `set(true)` would reject with `unsupported`, as in the player.
13696
+ * ```ts
13697
+ * import assert from "node:assert/strict";
13698
+ * import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
13699
+ * import activate from "../main.ts";
13700
+ *
13701
+ * const host = new TestHost();
13702
+ * host.upscaling.reason = { code: "no-gpu", message: "no NVIDIA GPU" };
13703
+ * const { stop } = await startPlugin(activate, {
13704
+ * directory: new URL("..", import.meta.url),
13705
+ * host,
13706
+ * });
13707
+ * assert.equal(host.upscaling.checked, false); // starting woke no GPU
13708
+ * const item = host.command("upscaling");
13709
+ * await item.run(); // the first pick checks the GPU
13710
+ * assert.equal(item.state.enabled, false); // greyed out
13711
+ * assert.equal(item.state.reason, "no NVIDIA GPU"); // the player's message
13712
+ * await stop();
13713
+ * ```
13714
+ */
13715
+ readonly upscaling: {
13716
+ /** Why upscaling does not work; `null` while it does. */
13717
+ reason: UpscalingReason | null;
13718
+ /** Whether this plugin has upscaling on (`set`); a test may preset it. */
13719
+ enabled: boolean;
13720
+ /** The factor the player would request; `null` or at most 1: the video needs no upscaling. */
13721
+ factor: number | null;
13722
+ /** Whether the plugin made the player check the GPU (`state`, `set(true)`). */
13723
+ checked: boolean;
13724
+ /** GPU rendering is still starting: a `rendering` reason then lets `set(true)` keep the request. */
13725
+ renderingStarting: boolean;
13726
+ };
13409
13727
  /** What `player.chapters()` lists while media is ready. */
13410
13728
  chapters: {
13411
13729
  /** The chapter title, `null` without one. */
@@ -14329,7 +14647,10 @@ menu last): their key paths, default bindings and owners. Use it to pick
14329
14647
  a menu place and default keys that do not collide with the player's (the
14330
14648
  user's own changes come on top; see Bindings). Menu items built
14331
14649
  from content (tracks, recent files, output devices) are not listed; their
14332
- placeholders (`No Audio Tracks`) are. The player's tests check that this
14650
+ placeholders (`No Audio Tracks`) are. Items that appear only in a
14651
+ passing state are not listed either: the Picture Adjustments plugin's
14652
+ `Video ▸ Restart Player to Apply` (`video.gpu-rendering.restart`), shown
14653
+ while a rendering choice waits for the next start. The player's tests check that this
14333
14654
  list agrees with what the player and its built-in plugins register.
14334
14655
 
14335
14656
  ```ts
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.35: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n>: a line of llms-full.txt (next to this file), which has every page, declaration and example.
3
+ > @vestra-player/sdk 1.5.37: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. Everything a plugin creates (commands, windows, panels, filters, listeners…) is a handle that is released automatically when the plugin stops. L<n>: a line of llms-full.txt (next to this file), which has every page, declaration and example.
4
4
 
5
5
  **Rules**
6
6
 
@@ -33,7 +33,7 @@ my-plugin/
33
33
  }
34
34
  ```
35
35
 
36
- Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2628–2697.
36
+ Fields: `id`, `version`, `apiVersion`, `entry`, `capabilities`, `name`, `author`, `punchline`, `description`, `tags`, `icon`, `readme`, `changelog`, `sourceUrl`, `license`, `minPlayerVersion`; unknown fields and capabilities are rejected. Rules and limits: L2636–2706.
37
37
 
38
38
  **main.ts**
39
39
 
@@ -77,9 +77,10 @@ export default async function activate(plugin: Plugin) {
77
77
  - `ui.theme`: Appearance.set/setLanguage
78
78
  - `secrets`: Secrets.get/set/delete
79
79
  - `rendering.manage`: Rendering.setPreference/restartPlayer
80
- - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details: L2698–2757.
80
+ - `video.upscaling`: VideoUpscaling.state/set
81
+ - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details: L2707–2767.
81
82
 
82
- **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`, `host` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L2069–2257; TestHost: L12483.
83
+ **Test**: `tests/*.test.ts` with `node:test`, run by `vestra-plugin test`. `startPlugin(activate, { directory: new URL("..", import.meta.url) })` from `@vestra-player/sdk/testing` returns `{ host, stop }`, `host` an in-memory player (TestHost): act (`host.command(id).run()`, `host.press(key)`), `await host.settle()`, assert on `host.osd`, `host.window(id).props`… Guide: L2077–2265; TestHost: L12744.
83
84
 
84
85
  **Command line**
85
86
 
@@ -107,39 +108,39 @@ export default async function activate(plugin: Plugin) {
107
108
  - `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
108
109
  - `reserved`: The id is `vestra` or starts with `vestra.`
109
110
  - `versionMismatch`: The package needs a newer player
110
- - Others: PluginError, llms-full.txt L3758.
111
+ - Others: PluginError, llms-full.txt L3770.
111
112
 
112
113
  ## Reference
113
114
 
114
115
  - [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; pages: [site](https://vestrasys.github.io/vestra-player-sdk/).
115
116
  - Tutorials: Getting started L9–237
116
- - Concepts: How a plugin runs L238–408; Commands, menus and key bindings L409–520; Playback state, controls and sources L521–666; Video and audio processing L667–745; Windows, panels, settings pages, dialogs and the OSD L746–1034
117
- - How-to: Control playback L1035–1163; Store a plugin's data L1164–1231; Load subtitles from a plugin L1232–1310; Keep a slider responsive while dragging L1311–1485; Remember a window's state between runs L1486–1615; Offer a GPU rendering switch L1616–1736; Translate a plugin L1737–1920; Migrate a plugin to English source text L1921–2068; Test a plugin L2069–2257; Publish a plugin L2258–2346; Migrating off package rollback (1.5.16, removed in 1.5.17) L2347–2422
118
- - Guidelines: Interface guidelines L2423–2530; Menu guidelines L2531–2627
119
- - Reference: Manifest L2628–2697; Capabilities L2698–2757; Limits L2758–2887; Commands, menus and bindings L2888–3370; Platforms L3371–3400; Theme L3401–3622; Command line L3623–3680
120
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3681–12167
121
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L12168–14823
117
+ - Concepts: How a plugin runs L238–408; Commands, menus and key bindings L409–520; Playback state, controls and sources L521–666; Video and audio processing L667–753; Windows, panels, settings pages, dialogs and the OSD L754–1042
118
+ - How-to: Control playback L1043–1171; Store a plugin's data L1172–1239; Load subtitles from a plugin L1240–1318; Keep a slider responsive while dragging L1319–1493; Remember a window's state between runs L1494–1623; Offer a GPU rendering switch L1624–1744; Translate a plugin L1745–1928; Migrate a plugin to English source text L1929–2076; Test a plugin L2077–2265; Publish a plugin L2266–2354; Migrating off package rollback (1.5.16, removed in 1.5.17) L2355–2430
119
+ - Guidelines: Interface guidelines L2431–2538; Menu guidelines L2539–2635
120
+ - Reference: Manifest L2636–2706; Capabilities L2707–2767; Limits L2768–2897; Commands, menus and bindings L2898–3381; Platforms L3382–3412; Theme L3413–3634; Command line L3635–3692
121
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3693–12428
122
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L12429–15144
122
123
 
123
124
  ## API
124
125
 
125
126
  Where each `plugin.<module>` is declared:
126
127
 
127
- - Plugin L4080, Scope L3833, PluginError L3758, Disposable L3696
128
- - Commands L4477; Windows L5414; Panels L6860; Dialogs L7528; Player L8269; video: VideoFilter L9149, VideoView L9249, RenderOptions L9336; audio: AudioFilter L9194; Subtitles L9483; media: MediaCapture L9887; Resolvers L10117; Packages L10515; Osd L9014; Host L8856; Clipboard L10843; Power L10930; Tools L10989; Storage L11141; System L11340; Secrets L11420; window: MainWindow L7177; App L7357; Rendering L11683; Bindings L10743; Ui L4766; Timeline L7089; Drops L6107; Appearance L6188; I18n L6421; Settings L6704.
128
+ - Plugin L4096, Scope L3845, PluginError L3770, Disposable L3708
129
+ - Commands L4493; Windows L5430; Panels L6876; Dialogs L7544; Player L8285; video: VideoFilter L9165, VideoView L9265, RenderOptions L9352, VideoUpscaling L9548; audio: AudioFilter L9210; Subtitles L9744; media: MediaCapture L10148; Resolvers L10378; Packages L10776; Osd L9030; Host L8872; Clipboard L11104; Power L11191; Tools L11250; Storage L11402; System L11601; Secrets L11681; window: MainWindow L7193; App L7373; Rendering L11944; Bindings L11004; Ui L4782; Timeline L7105; Drops L6123; Appearance L6204; I18n L6437; Settings L6720.
129
130
 
130
131
  ## Examples
131
132
 
132
133
  Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
133
134
 
134
- - hello: The README Quick start: one menu command and an on-screen message. L15258–15347
135
- - blur: A tool window with one slider that blurs the playing video. L15348–15609
136
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L15610–15942
137
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L15943–16129
138
- - panel-menu: A sidebar panel with its own right-click menu. L16130–16338
139
- - menus: Radio menu items, separator groups and a menu item whose title changes. L16339–16498
140
- - theme-widgets: A tool window drawn with the player's theme and widgets. L16499–16662
141
- - markdown: A window that shows a Markdown document. L16663–16859
142
- - i18n: A plugin in the player's language: English source text, a Chinese translation. L16860–17027
135
+ - hello: The README Quick start: one menu command and an on-screen message. L15579–15668
136
+ - blur: A tool window with one slider that blurs the playing video. L15669–15930
137
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L15931–16263
138
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L16264–16450
139
+ - panel-menu: A sidebar panel with its own right-click menu. L16451–16659
140
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L16660–16819
141
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L16820–16983
142
+ - markdown: A window that shows a Markdown document. L16984–17180
143
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L17181–17348
143
144
 
144
145
  ## Optional
145
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.35",
3
+ "version": "1.5.37",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {