@vestra-player/sdk 1.5.38 → 1.5.39

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/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.38: full reference
1
+ # @vestra-player/sdk 1.5.39: 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
 
@@ -2709,6 +2709,7 @@ The table lists the API whose documentation names a player version, newest last:
2709
2709
  | 0.5.12 | SystemInfo.rendering |
2710
2710
  | 0.5.13 | SystemInfo.renderingReason, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState, Rendering |
2711
2711
  | 0.5.16 | UpscalingReasonCode, UpscalingReason, UpscalingState, VideoUpscaling |
2712
+ | 0.5.20 | VideoShaders |
2712
2713
 
2713
2714
  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).
2714
2715
 
@@ -2730,7 +2731,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2730
2731
  | `ui.mount` | Panels.mount, Panels.bottom, Panels.sidebar, Panels.backdrop, PanelSlot.onChange, Panel.onChange, BackdropSlot.onChange | — | Show panels in the bottom bar, the sidebar or behind the video | |
2731
2732
  | `ui.dialog` | Dialogs.confirm, Dialogs.openFile, Dialogs.openFiles, Dialogs.openDirectory, Dialogs.saveFile | — | Show file pickers and confirmations | |
2732
2733
  | `plugins.manage` | Packages.validate, Packages.validateArchive, Packages.setEnabled, Packages.reload, Packages.uninstall | — | Install, uninstall and disable other plugins | yes |
2733
- | `processing.video` | Plugin.video.filter, Plugin.video.view, Plugin.video.render | `playback.read` | Process the picture (filters, shaders) | |
2734
+ | `processing.video` | Plugin.video.filter, Plugin.video.view, Plugin.video.render, Plugin.video.shaders | `playback.read` | Process the picture (filters, shaders) | |
2734
2735
  | `processing.audio` | Plugin.audio.filter | `playback.read`, `events` | Process the sound | |
2735
2736
  | `subtitles.read` | Subtitles.state, Subtitles.onChange, Subtitles.refresh | — | Read subtitles | |
2736
2737
  | `subtitles.control` | Subtitles.select, Subtitles.load, Subtitles.remove, Subtitles.setStyle | — | Load and change subtitles | |
@@ -2897,7 +2898,23 @@ RenderSettings; more shaders fail with `invalid`.
2897
2898
 
2898
2899
  | Limit | Value | Meaning |
2899
2900
  | --- | --- | --- |
2900
- | Entries of `RenderSettings.shaders` | 16 | mpv user shaders (`.glsl`): absolute paths of existing files, at most 16, in order. |
2901
+ | Entries of `RenderSettings.shaders` | 16 | mpv user shaders (`.glsl`): absolute paths of existing files, at most 16, in order. They run before the shaders of every plugin's `VideoShaders` handles. |
2902
+
2903
+ #### Video primitives
2904
+
2905
+ The `convolution` and `lut3d` VideoNodes and the shader channel VideoShaders (player 0.5.20). Values beyond them fail with `invalid`; more shaders than `shaders` across all plugins fail with `limit`.
2906
+
2907
+ | Limit | Value | Meaning |
2908
+ | --- | --- | --- |
2909
+ | `VIDEO_PROCESSING_LIMITS.shaders` | 16 | Shaders across every plugin's `plugin.video.shaders()` handles. |
2910
+ | `VIDEO_PROCESSING_LIMITS.shaderBytes` | 16777216 (16 MiB) | Bytes of one shader file. |
2911
+ | `VIDEO_PROCESSING_LIMITS.shaderIdBytes` | 128 | Bytes of a shader handle's id. |
2912
+ | `VIDEO_PROCESSING_LIMITS.lutBytes` | 16777216 (16 MiB) | Bytes of one `lut3d` `.cube` file. |
2913
+ | `VIDEO_PROCESSING_LIMITS.lutSize` | 65 | Largest `LUT_3D_SIZE` of a `lut3d` file, points per axis. |
2914
+ | `VIDEO_PROCESSING_LIMITS.lutPathBytes` | 4096 (4 KiB) | Bytes of a `lut3d` file path. |
2915
+ | `VIDEO_PROCESSING_LIMITS.matrixValue` | 1024 | Largest magnitude of a `convolution` matrix entry. |
2916
+ | `VIDEO_PROCESSING_LIMITS.rdiv` | 256 | Largest `convolution` `rdiv`. |
2917
+ | `VIDEO_PROCESSING_LIMITS.bias` | 65535 | Largest `convolution` `bias`. |
2901
2918
 
2902
2919
  #### Limits documented at their API
2903
2920
 
@@ -3889,7 +3906,7 @@ interface Scope extends Disposable {
3889
3906
  /** Playback state and controls (`playback.read`, `events`, `playback.control`). */
3890
3907
  readonly player: Player;
3891
3908
  /**
3892
- * 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.
3909
+ * Video processing: this plugin's filter chain, the display layer, the GPU render options, this plugin's shaders and the GPU's video upscaler. The first four 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.
3893
3910
  */
3894
3911
  readonly video: {
3895
3912
  /**
@@ -3910,6 +3927,15 @@ interface Scope extends Disposable {
3910
3927
  * @throws Error without a code (throws) A second call in the same plugin while the first handle is not disposed.
3911
3928
  */
3912
3929
  render(): RenderOptions;
3930
+ /**
3931
+ * This plugin's own GPU shaders named `id` (default `"shaders"`; needs `processing.video`; SDK 1.5.39, player 0.5.20). Unlike render(), every plugin may hold shader handles at the same time; the player joins their lists. Nothing is sent until the first `set()`.
3932
+ * @param id - Local name of the handle, 1–128 bytes, unique within the plugin.
3933
+ * @returns A VideoShaders owned by this scope.
3934
+ * @throws capability (throws) The manifest does not declare `processing.video`.
3935
+ * @throws invalid (throws) `id` is empty, longer than 128 bytes or has control characters.
3936
+ * @throws Error without a code (throws) A second handle with the same `id` while the first is not disposed.
3937
+ */
3938
+ shaders(id?: string): VideoShaders;
3913
3939
  /**
3914
3940
  * 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.
3915
3941
  */
@@ -7455,8 +7481,10 @@ interface SystemInfo {
7455
7481
  * Absent (`undefined`) on players before 0.5.12, which do not report it:
7456
7482
  * the renderer is then unknown, not software.
7457
7483
  *
7458
- * Today the player renders on the GPU on macOS and in software on
7459
- * Windows, Linux and without a user interface (see
7484
+ * Today the player renders on the GPU on macOS, on Windows when the
7485
+ * user turned on Video ▸ GPU Rendering (Experimental) (since version 0.5.14;
7486
+ * off by default), and in software otherwise: on Windows by default, on
7487
+ * Linux and without a user interface (see
7460
7488
  * Platforms); test this field
7461
7489
  * rather than SystemInfo.os.
7462
7490
  * @player 0.5.12; older players leave it out (declare `"minPlayerVersion": "0.5.12"` when the plugin relies on it).
@@ -8223,7 +8251,7 @@ What Player.stats reports. Every field is the backend's own value
8223
8251
  ```ts
8224
8252
  interface PlaybackStats {
8225
8253
  /**
8226
- * The rendering path: "libmpv · GPU 渲染(OpenGL)" (GPU, macOS default) or "libmpv · 软件渲染" (software: Windows, Linux, headless, fallback). The strings are the player's display text.
8254
+ * The rendering path: "libmpv · GPU 渲染(OpenGL)" (GPU on macOS, the default there), "libmpv · GPU 渲染(Direct3D)" (GPU on Windows, when turned on; version 0.5.14 and later) or "libmpv · 软件渲染" (software: Windows by default, Linux, headless, fallback). The strings are the player's display text in the interface language (shown here in Chinese).
8227
8255
  */
8228
8256
  backend: string;
8229
8257
  /** The playback session number. */
@@ -9352,7 +9380,7 @@ interface RenderSettings {
9352
9380
  targetPeak: number;
9353
9381
  /** Per-scene peak estimation (where the GPU supports it). */
9354
9382
  peakDetect: boolean;
9355
- /** mpv user shaders (`.glsl`): absolute paths of existing files, at most 16, in order. */
9383
+ /** mpv user shaders (`.glsl`): absolute paths of existing files, at most 16, in order. They run before the shaders of every plugin's VideoShaders handles. */
9356
9384
  shaders: string[];
9357
9385
  /** Motion interpolation, resampled to the display's refresh rate. */
9358
9386
  interpolation: boolean;
@@ -9365,19 +9393,23 @@ The GPU renderer's options, from `plugin.video.render()`
9365
9393
  (`processing.video`). Single owner, like VideoView; the two are
9366
9394
  independent handles.
9367
9395
 
9368
- Platforms: only macOS renders on the GPU. Under software rendering
9369
- (Windows, Linux, headless, fallback) every `set` fails with
9370
- `unsupported`; SystemInfo.rendering tells which renderer draws
9371
- without creating the handle. macOS OpenGL 4.1 has no
9396
+ Platforms: macOS renders on the GPU by default; Windows does since
9397
+ version 0.5.14 of the player when the user turns on Video ▸ GPU Rendering
9398
+ (Experimental) (off by default, Rendering). Under software
9399
+ rendering (Windows by default, Linux, headless, fallback) every `set`
9400
+ fails with `unsupported`; SystemInfo.rendering tells which
9401
+ renderer draws without creating the handle. macOS OpenGL 4.1 has no
9372
9402
  compute shaders, so `peakDetect` is accepted but has no effect; output
9373
9403
  is always SDR.
9374
9404
 
9375
9405
  GPU renderer options (tone mapping, peak detection, shaders,
9376
- interpolation). Only the GPU renderer has them, which today means macOS;
9406
+ interpolation). Only the GPU renderer has them (macOS; Windows with GPU
9407
+ rendering turned on, version 0.5.14 and later);
9377
9408
  under software rendering the handle is `failed` with `unsupported` after
9378
9409
  the player's first answer to `render()` (reported to `onError` too), and so is
9379
9410
  every `set`. Single owner, like VideoView. `set` never throws; the
9380
- result shows in `status`.
9411
+ result shows in `status`. For shaders that several plugins add at the
9412
+ same time, use VideoShaders (SDK 1.5.39).
9381
9413
 
9382
9414
  - @capability processing.video, playback.read and events (like VideoFilter)
9383
9415
 
@@ -9395,7 +9427,9 @@ interface RenderOptions extends Disposable {
9395
9427
  * ```
9396
9428
  */
9397
9429
  set(value: Partial<RenderSettings>): void;
9398
- /** `failed` with `unsupported` while the player renders in software; `failed` with `failed` when mpv cannot compile a shader (pushed later). */
9430
+ /**
9431
+ * `failed` with `unsupported` while the player renders in software; `failed` with `failed` when mpv cannot compile a shader (pushed later; while other plugins' VideoShaders are on too, the player cannot tell whose shader failed and says so in the message).
9432
+ */
9399
9433
  readonly status: FilterStatus;
9400
9434
  /**
9401
9435
  * The status changed (including later pushes such as shader compile errors).
@@ -9421,7 +9455,8 @@ On a GPU-rendering player HDR video is tone-mapped with BT.2390 for a
9421
9455
  250-nit display; under software rendering the plugin hides its HDR menu.
9422
9456
  `set` merges the values into the plugin's render options and the
9423
9457
  player reports the result through `onStatus`: `failed` with
9424
- `unsupported` means the player renders in software (Windows, Linux).
9458
+ `unsupported` means the player renders in software (Linux, or Windows
9459
+ while GPU rendering is off).
9425
9460
  ```ts
9426
9461
  const render = plugin.video.render();
9427
9462
  render.onStatus((s) => {
@@ -9430,6 +9465,127 @@ render.onStatus((s) => {
9430
9465
  render.set({ toneMapping: "bt.2390", targetPeak: 250 }); // nits
9431
9466
  ```
9432
9467
 
9468
+ ### VideoShaders
9469
+
9470
+ This plugin's own GPU shaders, from `plugin.video.shaders(id)`
9471
+ (`processing.video`, SDK 1.5.39, player 0.5.20): mpv user shaders
9472
+ (`.glsl`, `//!HOOK` …) that the GPU renderer runs on every frame.
9473
+
9474
+ Any number of plugins hold shader handles at the same time (unlike
9475
+ RenderOptions, which has one owner). The player joins every
9476
+ handle's list in a stable order, after the render options' own
9477
+ `shaders`: built-in plugins first, then installed plugins in the order
9478
+ they were first installed, then development loads (`--plugin`); ties by
9479
+ plugin id, and a plugin's handles by `id`. At most 16 shaders across all
9480
+ handles (`limit` beyond, nothing changes); the render options keep their
9481
+ own 16.
9482
+
9483
+ Files must be this plugin's own: absolute paths inside its package
9484
+ directory or Plugin.dataDirectory after following links, each
9485
+ at most 16 MiB. Elsewhere: `invalid`; a file that does not exist:
9486
+ `missing`. The player reads the files when the list is set.
9487
+
9488
+ GPU rendering only (macOS; Windows with Video ▸ GPU Rendering
9489
+ (Experimental) turned on): under software rendering a non-empty list is
9490
+ `failed` with `unsupported`, and a switch to software during the run
9491
+ pushes `unsupported` to every handle with shaders and removes every
9492
+ list (they no longer count against the 16); set them again once the
9493
+ player draws on the GPU (after a restart). Shaders act when a
9494
+ frame is drawn, so processed snapshots and exports never contain them
9495
+ (use a VideoNode for that). When mpv cannot compile a shader the
9496
+ handle turns `failed` with `failed` (pushed later); mpv does not say
9497
+ which file failed, so while several plugins' shaders are on, each of
9498
+ them hears it and the message says the player cannot tell whose.
9499
+
9500
+ `set` sends the whole list (last write wins); `clear` and `dispose`
9501
+ withdraw it; the player removes a stopped plugin's shaders itself.
9502
+ Players before 0.5.20 refuse the request (the handle is `failed`): declare
9503
+ `"minPlayerVersion": "0.5.20"`.
9504
+
9505
+ - @capability processing.video
9506
+ - @player 0.5.20; older players refuse the request.
9507
+
9508
+ ```ts
9509
+ interface VideoShaders extends Disposable {
9510
+ /**
9511
+ * Replace this handle's shader list (last write wins); `[]` withdraws it.
9512
+ *
9513
+ * Never throws: a refused list shows in `status` (`invalid`, `missing`,
9514
+ * `limit`, `unsupported`) and goes to Scope.onError, and what the
9515
+ * player held stays.
9516
+ * @capability processing.video
9517
+ * @param paths - Absolute paths of shader files in this plugin's package or data directory, in the order they run.
9518
+ * @example Switch between two shader presets
9519
+ * The plugin offers a light and a strong preset; picking one replaces
9520
+ * the handle's list in one request. The files were written to the
9521
+ * plugin's data directory when the user imported them.
9522
+ * ```ts
9523
+ * const presets: Record<string, string[]> = {
9524
+ * light: [join(plugin.dataDirectory, "denoise.glsl")],
9525
+ * strong: [join(plugin.dataDirectory, "denoise.glsl"), join(plugin.dataDirectory, "sharpen.glsl")],
9526
+ * };
9527
+ * plugin.video.shaders("presets").set(presets.strong!);
9528
+ * ```
9529
+ */
9530
+ set(paths: readonly string[]): void;
9531
+ /**
9532
+ * Withdraw this handle's shaders (the same as `set([])`).
9533
+ * @capability processing.video
9534
+ * @example Turn the plugin's shaders off from a menu item
9535
+ * A Video menu item turns the plugin's shader on and off. Clearing
9536
+ * keeps the handle, so the next pick sets the list again.
9537
+ * ```ts
9538
+ * const shaders = plugin.video.shaders();
9539
+ * let on = false;
9540
+ * plugin.commands.add({
9541
+ * id: "toggle-shader",
9542
+ * path: ["@video", "Sharpen (Shader)"],
9543
+ * checked: () => on,
9544
+ * run: () => {
9545
+ * on = !on;
9546
+ * // Clearing keeps the handle; the next pick sets the list again.
9547
+ * if (on) shaders.set([file]);
9548
+ * else shaders.clear();
9549
+ * },
9550
+ * });
9551
+ * ```
9552
+ */
9553
+ clear(): void;
9554
+ /** Current status: `waiting` before ready media, `applying`, `applied`, or `failed` with the reason (see the remarks). */
9555
+ readonly status: FilterStatus;
9556
+ /**
9557
+ * The status changed, including later pushes (a compile failure, a switch to software rendering).
9558
+ * @capability processing.video
9559
+ * @example Report a shader that does not compile
9560
+ * The OSD shows mpv's message when the plugin's shader fails to compile.
9561
+ * The player pushes that failure after the list was accepted, so it
9562
+ * arrives through `onStatus`, not as a refused `set`.
9563
+ * ```ts
9564
+ * const shaders = plugin.video.shaders();
9565
+ * shaders.onStatus((status) => {
9566
+ * if (status.state === "failed") void plugin.osd.show("Shader: {}", [status.error.message]);
9567
+ * });
9568
+ * ```
9569
+ */
9570
+ onStatus(listener: (status: FilterStatus) => void): Disposable;
9571
+ }
9572
+ ```
9573
+
9574
+ Example: Add a sharpening shader the plugin ships
9575
+
9576
+ The video looks sharper on the GPU renderer; under software rendering
9577
+ the OSD says why it is not. The shader file lives in the plugin's
9578
+ package, and the player puts it after other plugins' shaders that come
9579
+ earlier in its order.
9580
+ ```ts
9581
+ const shaders = plugin.video.shaders("sharpen");
9582
+ shaders.onStatus((s) => {
9583
+ // `unsupported`: software rendering; `failed`: mpv could not compile it.
9584
+ if (s.state === "failed") void plugin.osd.show("Sharpen: {}", [s.error.message]);
9585
+ });
9586
+ shaders.set([join(packageDirectory, "shaders", "sharpen.glsl")]);
9587
+ ```
9588
+
9433
9589
  ### UpscalingReasonCode
9434
9590
 
9435
9591
  Why the GPU's video upscaler does not work, as a stable kebab-case code.
@@ -11849,10 +12005,10 @@ RenderingReason.message for the rest. The codes of player 0.5.13:
11849
12005
 
11850
12006
  | Code | Meaning |
11851
12007
  | --- | --- |
11852
- | `default` | Nothing ruled the GPU out; the platform's default. |
12008
+ | `default` | Nothing ruled the GPU out; the platform's default (macOS: GPU; Windows since version 0.5.14: software until the user turns on GPU rendering). |
11853
12009
  | `pending` | GPU rendering is starting. |
11854
12010
  | `headless` | The player runs without a user interface. |
11855
- | `no-gpu-backend` | This player has no GPU renderer on this system (Windows today). |
12011
+ | `no-gpu-backend` | This player has no GPU renderer on this system (Linux today; Windows has one since version 0.5.14, off by default, which reports `default`). |
11856
12012
  | `env` | The environment chose it (`VESTRA_RENDER`, `VESTRA_SOFTWARE_RENDER`). |
11857
12013
  | `preference` | The user chose software rendering (Rendering.setPreference); GPU chosen and available reports `default`. |
11858
12014
  | `denylist` | The graphics driver is on the player's denylist. |
@@ -12217,6 +12373,28 @@ type VideoNode =
12217
12373
  kind: "frameBlend";
12218
12374
  /** Output frame rate, integer 24–120. */
12219
12375
  fps: number;
12376
+ }
12377
+ /**
12378
+ * A convolution matrix of your own per plane (FFmpeg `convolution`, SDK 1.5.39, player 0.5.20): sharpen, blur, emboss or detect edges with a 3×3, 5×5 or 7×7 kernel, or a row or column kernel. `planes[i]` is plane `i` of the decoded picture: for the usual YUV video 0 is luma (brightness), 1 and 2 are the colour planes, 3 is alpha. A missing entry or `null` leaves that plane as it is; at least one entry has a matrix. Players before 0.5.20 refuse the kind with `invalid`.
12379
+ */
12380
+ | {
12381
+ /** The node type. */
12382
+ kind: "convolution";
12383
+ /** 1–4 entries, by plane; `null` passes the plane through. */
12384
+ planes: (ConvolutionPlane | null)[];
12385
+ }
12386
+ /**
12387
+ * Recolour the picture with a 3D look-up table (FFmpeg `lut3d`, SDK 1.5.39, player 0.5.20): a `.cube` file the plugin ships in its package or wrote to its Plugin.dataDirectory. The player reads the file when the chain is set (and again for each new source, snapshot or export), so it must stay there while the chain uses it. The table is applied to RGB; the player converts the picture to RGB and back. Players before 0.5.20 refuse the kind with `invalid`.
12388
+ */
12389
+ | {
12390
+ /** The node type. */
12391
+ kind: "lut3d";
12392
+ /**
12393
+ * Absolute path of a `.cube` file (`LUT_3D_SIZE` 2–65, at most 16 MiB) inside the plugin's package directory or data directory after following links; a path with `[` or `]` is refused. Outside those: `invalid`; a file that does not exist: `missing`.
12394
+ */
12395
+ file: string;
12396
+ /** How colours between the table's points are computed; `tetrahedral` is FFmpeg's default. */
12397
+ interpolation: "nearest" | "trilinear" | "tetrahedral" | "pyramid" | "prism";
12220
12398
  };
12221
12399
  ```
12222
12400
 
@@ -12238,6 +12416,27 @@ const chain: VideoNode[] = [
12238
12416
  plugin.video.filter("picture").set(chain);
12239
12417
  ```
12240
12418
 
12419
+ ### ConvolutionPlane
12420
+
12421
+ One plane's kernel of a `convolution` VideoNode: each output
12422
+ sample is `(sum of matrix × neighbourhood) × rdiv + bias`, clipped to the
12423
+ sample range.
12424
+
12425
+ ```ts
12426
+ type ConvolutionPlane = {
12427
+ /**
12428
+ * `square`: `matrix` is a 3×3, 5×5 or 7×7 kernel, row by row (9, 25 or 49 entries); `row` / `column`: a horizontal / vertical kernel with an odd number of entries up to 49.
12429
+ */
12430
+ mode: "square" | "row" | "column";
12431
+ /** Integers −1024–1024 (FFmpeg reads whole numbers only). */
12432
+ matrix: number[];
12433
+ /** Multiplier 0–256; 0 divides by the sum of the matrix (by 1 when it sums to 0), which keeps the brightness of a blur. */
12434
+ rdiv: number;
12435
+ /** Added after the multiplication, 0–65535 in the plane's sample units (128 is mid grey for 8-bit video). */
12436
+ bias: number;
12437
+ };
12438
+ ```
12439
+
12241
12440
  ### AudioNode
12242
12441
 
12243
12442
  One node of an AudioFilter chain, by `kind`. Values outside the
@@ -13394,6 +13593,39 @@ class TestHost implements Transport {
13394
13593
  * ```
13395
13594
  */
13396
13595
  failVideoRender(code: "failed" | "unsupported", message: string): void;
13596
+ /**
13597
+ * The shader channel (`plugin.video.shaders(id)`, SDK 1.5.39): the lists the player holds for this plugin's handles by id (the real paths, after following links), and how many shaders other plugins hold, which count against VIDEO_PROCESSING_LIMITS`.shaders` (`limit`). Files must be under TestHost.fileRoots; under software rendering (`videoRender.gpu = false`) a non-empty list fails with `unsupported`.
13598
+ */
13599
+ readonly videoShaders: {
13600
+ /** This plugin's lists by handle id; a cleared handle has none. */
13601
+ handles: Map<string, string[]>;
13602
+ /** Shaders other plugins hold (0–16). */
13603
+ others: number;
13604
+ };
13605
+ /**
13606
+ * Where the plugin's own files may be: its package directory and its data directory (startPlugin sets both when the list is still empty). `lut3d` files and shader-channel files outside them fail with `invalid`, as in the player.
13607
+ */
13608
+ fileRoots: string[];
13609
+ /**
13610
+ * What the player pushes to a shader handle when mpv cannot compile a shader (code `failed`; the player cannot tell whose shader failed, so every handle with shaders hears it) or the player switches to software rendering (`unsupported`; use TestHost.failVideoRender, which also tells the handles). Pushed only to a handle that holds shaders.
13611
+ * @capability None.
13612
+ * @example Make the plugin's shader fail to compile
13613
+ * The plugin's shader handle reports `failed`, as when mpv rejects a
13614
+ * shader. The host pushes the failure only to a handle that holds
13615
+ * shaders, which its accepted `set` made it; the SDK updates the
13616
+ * handle's `status` when the push arrives, so the test settles first.
13617
+ * ```ts
13618
+ * const file = join(plugin.dataDirectory, "sharpen.glsl");
13619
+ * writeFileSync(file, "//!HOOK MAIN\n");
13620
+ * const shaders = plugin.video.shaders("sharpen");
13621
+ * shaders.set([file]); // the handle now holds a shader
13622
+ * await host.settle();
13623
+ * host.failVideoShaders("sharpen", "shader compile failed: ERROR: 0:3: ...");
13624
+ * await host.settle();
13625
+ * assert.equal(shaders.status.state, "failed");
13626
+ * ```
13627
+ */
13628
+ failVideoShaders(id: string, message: string): void;
13397
13629
  /**
13398
13630
  * The player's processing (filter) state. Wire-level; may change in minor versions. `resources` holds each `video.filter` / `audio.filter` chain the plugin applied, keyed by the SDK's resource id.
13399
13631
  * @example Check the filter chain the plugin applied
@@ -13655,9 +13887,9 @@ class TestHost implements Transport {
13655
13887
  * import activate from "../main.ts";
13656
13888
  *
13657
13889
  * const host = new TestHost();
13658
- * host.videoRender.gpu = false; // software draws now (Windows)
13890
+ * host.videoRender.gpu = false; // software draws now (Linux)
13659
13891
  * host.rendering.startedWith = "software";
13660
- * host.rendering.reason = { code: "no-gpu-backend", message: "No GPU renderer on Windows" };
13892
+ * host.rendering.reason = { code: "no-gpu-backend", message: "this player has no GPU renderer on Linux yet" };
13661
13893
  * host.rendering.gpu = { available: false, reason: host.rendering.reason };
13662
13894
  * const { stop } = await startPlugin(activate, {
13663
13895
  * directory: new URL("..", import.meta.url),
@@ -13665,7 +13897,7 @@ class TestHost implements Transport {
13665
13897
  * });
13666
13898
  * const item = host.command("gpu-rendering");
13667
13899
  * assert.equal(item.state.enabled, false);
13668
- * assert.equal(item.state.reason, "No GPU renderer on Windows");
13900
+ * assert.equal(item.state.reason, "this player has no GPU renderer on Linux yet");
13669
13901
  * await stop();
13670
13902
  * ```
13671
13903
  */
@@ -15100,6 +15332,49 @@ const IMAGE_LIMITS: Readonly<{
15100
15332
  }>;
15101
15333
  ```
15102
15334
 
15335
+ ### VIDEO_PROCESSING_LIMITS
15336
+
15337
+ The player's bounds for the video primitives of SDK 1.5.39 (player
15338
+ 0.5.20): `convolution` and `lut3d` VideoNodes and the shader
15339
+ channel (`plugin.video.shaders()`). Values beyond them fail with
15340
+ `invalid`; more shaders than `shaders` fail with `limit`.
15341
+
15342
+ ```ts
15343
+ const VIDEO_PROCESSING_LIMITS: Readonly<{
15344
+ /** Shaders across every plugin's `plugin.video.shaders()` handles (16). */
15345
+ shaders: 16;
15346
+ /** Bytes of one shader file (16 MiB). */
15347
+ shaderBytes: number;
15348
+ /** Bytes of a shader handle's id (128). */
15349
+ shaderIdBytes: 128;
15350
+ /** Bytes of one `lut3d` `.cube` file (16 MiB). */
15351
+ lutBytes: number;
15352
+ /** Largest `LUT_3D_SIZE` of a `lut3d` file, points per axis (65). */
15353
+ lutSize: 65;
15354
+ /** Bytes of a `lut3d` file path (4096). */
15355
+ lutPathBytes: 4096;
15356
+ /** Largest magnitude of a `convolution` matrix entry (1024). */
15357
+ matrixValue: 1024;
15358
+ /** Largest `convolution` `rdiv` (256). */
15359
+ rdiv: 256;
15360
+ /** Largest `convolution` `bias` (65535). */
15361
+ bias: 65535;
15362
+ }>;
15363
+ ```
15364
+
15365
+ Example: Refuse a shader list the player would not take
15366
+
15367
+ The plugin checks a shader list against the player's limit before it
15368
+ sets it, and says so on the OSD instead of waiting for a `limit` failure.
15369
+ The limit counts every plugin's shaders, so the player still decides.
15370
+ ```ts
15371
+ import { VIDEO_PROCESSING_LIMITS } from "@vestra-player/sdk/testing";
15372
+
15373
+ if (files.length > VIDEO_PROCESSING_LIMITS.shaders)
15374
+ void plugin.osd.show("Too many shaders");
15375
+ else plugin.video.shaders().set(files);
15376
+ ```
15377
+
15103
15378
  ### HostError
15104
15379
 
15105
15380
  A definite refusal from the player (or the plugin runner), with a stable
@@ -15359,7 +15634,7 @@ What Player.stats reports. Every field is the backend's own value
15359
15634
  ```ts
15360
15635
  interface PlaybackStats {
15361
15636
  /**
15362
- * The rendering path: "libmpv · GPU 渲染(OpenGL)" (GPU, macOS default) or "libmpv · 软件渲染" (software: Windows, Linux, headless, fallback). The strings are the player's display text.
15637
+ * The rendering path: "libmpv · GPU 渲染(OpenGL)" (GPU on macOS, the default there), "libmpv · GPU 渲染(Direct3D)" (GPU on Windows, when turned on; version 0.5.14 and later) or "libmpv · 软件渲染" (software: Windows by default, Linux, headless, fallback). The strings are the player's display text in the interface language (shown here in Chinese).
15363
15638
  */
15364
15639
  backend: string;
15365
15640
  /** The playback session number. */
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.38: 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.39: 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: L2646–2716.
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: L2646–2717.
37
37
 
38
38
  **main.ts**
39
39
 
@@ -61,7 +61,7 @@ export default async function activate(plugin: Plugin) {
61
61
  - `ui.mount`: Panels.mount/bottom/sidebar/backdrop, PanelSlot/Panel/BackdropSlot.onChange
62
62
  - `ui.dialog`: Dialogs.confirm/openFile/openFiles/openDirectory/saveFile
63
63
  - `plugins.manage`: Packages.validate/validateArchive/setEnabled/reload/uninstall
64
- - `processing.video`: Plugin.video.filter/video.view/video.render (also `playback.read`)
64
+ - `processing.video`: Plugin.video.filter/video.view/video.render/video.shaders (also `playback.read`)
65
65
  - `processing.audio`: Plugin.audio.filter (also `playback.read`, `events`)
66
66
  - `subtitles.read`: Subtitles.state/onChange/refresh
67
67
  - `subtitles.control`: Subtitles.select/load/remove/setStyle
@@ -78,9 +78,9 @@ export default async function activate(plugin: Plugin) {
78
78
  - `secrets`: Secrets.get/set/delete
79
79
  - `rendering.manage`: Rendering.setPreference/restartPlayer
80
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: L2717–2777.
81
+ - No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme, packages.list and rendering.state. Details: L2718–2778.
82
82
 
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: L2087–2275; TestHost: L12758.
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: L2087–2275; TestHost: L12957.
84
84
 
85
85
  **Command line**
86
86
 
@@ -108,7 +108,7 @@ export default async function activate(plugin: Plugin) {
108
108
  - `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
109
109
  - `reserved`: The id is `vestra` or starts with `vestra.`
110
110
  - `versionMismatch`: The package needs a newer player
111
- - Others: PluginError, llms-full.txt L3780.
111
+ - Others: PluginError, llms-full.txt L3797.
112
112
 
113
113
  ## Reference
114
114
 
@@ -117,30 +117,30 @@ export default async function activate(plugin: Plugin) {
117
117
  - Concepts: How a plugin runs L238–418; Commands, menus and key bindings L419–530; Playback state, controls and sources L531–676; Video and audio processing L677–763; Windows, panels, settings pages, dialogs and the OSD L764–1052
118
118
  - How-to: Control playback L1053–1181; Store a plugin's data L1182–1249; Load subtitles from a plugin L1250–1328; Keep a slider responsive while dragging L1329–1503; Remember a window's state between runs L1504–1633; Offer a GPU rendering switch L1634–1754; Translate a plugin L1755–1938; Migrate a plugin to English source text L1939–2086; Test a plugin L2087–2275; Publish a plugin L2276–2364; Migrating off package rollback (1.5.16, removed in 1.5.17) L2365–2440
119
119
  - Guidelines: Interface guidelines L2441–2548; Menu guidelines L2549–2645
120
- - Reference: Manifest L2646–2716; Capabilities L2717–2777; Limits L2778–2907; Commands, menus and bindings L2908–3391; Platforms L3392–3422; Theme L3423–3644; Command line L3645–3702
121
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3703–12442
122
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L12443–15162
120
+ - Reference: Manifest L2646–2717; Capabilities L2718–2778; Limits L2779–2924; Commands, menus and bindings L2925–3408; Platforms L3409–3439; Theme L3440–3661; Command line L3662–3719
121
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3720–12641
122
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L12642–15437
123
123
 
124
124
  ## API
125
125
 
126
126
  Where each `plugin.<module>` is declared:
127
127
 
128
- - Plugin L4106, Scope L3855, PluginError L3780, Disposable L3718
129
- - Commands L4503; Windows L5440; Panels L6886; Dialogs L7554; Player L8295; video: VideoFilter L9175, VideoView L9275, RenderOptions L9362, VideoUpscaling L9558; audio: AudioFilter L9220; Subtitles L9754; media: MediaCapture L10158; Resolvers L10388; Packages L10790; Osd L9040; Host L8882; Clipboard L11118; Power L11205; Tools L11264; Storage L11416; System L11615; Secrets L11695; window: MainWindow L7203; App L7383; Rendering L11958; Bindings L11018; Ui L4792; Timeline L7115; Drops L6133; Appearance L6214; I18n L6447; Settings L6730.
128
+ - Plugin L4132, Scope L3872, PluginError L3797, Disposable L3735
129
+ - Commands L4529; Windows L5466; Panels L6912; Dialogs L7582; Player L8323; video: VideoFilter L9203, VideoView L9303, RenderOptions L9390, VideoShaders L9468, VideoUpscaling L9714; audio: AudioFilter L9248; Subtitles L9910; media: MediaCapture L10314; Resolvers L10544; Packages L10946; Osd L9068; Host L8910; Clipboard L11274; Power L11361; Tools L11420; Storage L11572; System L11771; Secrets L11851; window: MainWindow L7229; App L7409; Rendering L12114; Bindings L11174; Ui L4818; Timeline L7141; Drops L6159; Appearance L6240; I18n L6473; Settings L6756.
130
130
 
131
131
  ## Examples
132
132
 
133
133
  Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
134
134
 
135
- - hello: The README Quick start: one menu command and an on-screen message. L15597–15686
136
- - blur: A tool window with one slider that blurs the playing video. L15687–15948
137
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L15949–16281
138
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L16282–16468
139
- - panel-menu: A sidebar panel with its own right-click menu. L16469–16677
140
- - menus: Radio menu items, separator groups and a menu item whose title changes. L16678–16837
141
- - theme-widgets: A tool window drawn with the player's theme and widgets. L16838–17001
142
- - markdown: A window that shows a Markdown document. L17002–17198
143
- - i18n: A plugin in the player's language: English source text, a Chinese translation. L17199–17366
135
+ - hello: The README Quick start: one menu command and an on-screen message. L15872–15961
136
+ - blur: A tool window with one slider that blurs the playing video. L15962–16223
137
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L16224–16556
138
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L16557–16743
139
+ - panel-menu: A sidebar panel with its own right-click menu. L16744–16952
140
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L16953–17112
141
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L17113–17276
142
+ - markdown: A window that shows a Markdown document. L17277–17473
143
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L17474–17641
144
144
 
145
145
  ## Optional
146
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.38",
3
+ "version": "1.5.39",
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": {