@vestra-player/sdk 1.5.37 → 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.37: 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
 
@@ -329,11 +329,11 @@ The rule that follows: **state you change outside a handler, or after an `await`
329
329
 
330
330
  #### Stopping
331
331
 
332
- The player stops a plugin when the user disables or uninstalls it, when it is reloaded or upgraded, and when the player quits.
332
+ The player stops a plugin when the user disables or uninstalls it, when it is reloaded or upgraded, when the player quits, and, with lazy activation, when nothing has used it for a while (Idle plugins).
333
333
 
334
334
  1. The player first removes everything the plugin owns on its side: menu items, windows, panels, settings pages, filter chains, subtitle layers, snapshots, export jobs, resolvers, generated streams.
335
335
  2. The SDK then refuses new requests, fails pending ones with `code: "disabled"` and disposes the root scope. That disposal only cleans up locally (timers, abort signals, sockets): the player-side resources are already gone.
336
- 3. A process that has not exited 300 ms later is killed.
336
+ 3. A process that has not exited 300 ms later is killed (2 s later when it stops because it is idle).
337
337
 
338
338
  So `onDispose` cleanup must be quick and must not rely on the player. Media the plugin opened with player.open **keeps playing** after it stops.
339
339
 
@@ -344,7 +344,7 @@ A plugin's process does not live as long as the player. Within one player sessio
344
344
  - the plugin is **reloaded** (from the plugin manager, or a development load with `--plugin`) or **upgraded**;
345
345
  - it **crashed**, or failed to start, and is started again.
346
346
 
347
- A later player may restart a plugin in more cases: restarting plugins that share one process together when one of them faults or hangs, or stopping a plugin nobody uses and starting it on its next use. Write the plugin for those too.
347
+ A later player may restart a plugin in more cases: restarting plugins that share one process together when one of them faults or hangs, or stopping a plugin nobody uses and starting it on its next use (Idle plugins). Write the plugin for those too.
348
348
 
349
349
  Everything in the process is gone after such a restart: variables, timers, caches, open sockets. The menus, windows and panels come back because `activate` creates them again. **Keep state that must survive a restart in session storage (or persistent storage):**
350
350
 
@@ -358,6 +358,16 @@ Session storage is for state that belongs to this run of the player: a queue the
358
358
 
359
359
  Write `activate` so that running it twice in one session is normal: read what you kept, then build the interface from it.
360
360
 
361
+ #### Idle plugins are stopped
362
+
363
+ With lazy activation (player 0.5.18 and later, while the player turns it on), the player works like a browser's extension service workers (Chrome Manifest V3): a plugin that holds nothing only its process can serve is stopped after about a minute without use, and started again by its next use.
364
+
365
+ - **What stays while it is stopped**: its menu items and keys (with the last `enabled`, `checked` and `reason` it set), its settings pages, its file and link resolvers and its drop targets. Using one of them, or showing one of its settings pages, starts the plugin; it runs `activate`, registers them again, and then receives the click, the open or the drop. The player also remembers these between runs, so a plugin nobody uses is not started at launch at all.
366
+ - **What keeps it running**: anything the player must deliver to it or that is its running work: a listener (`player.onChange`, `window.onChange`, `bindings.onChange`, `packages.onChange` and the other `onChange`, `onEnd`), a command state, panel or window binding that reads `plugin.player.state` or another state, an open window, a mounted panel, an OSD panel, a timeline layer, a filter chain, a stream or subtitle track it added, a next/previous provider, a sleep lease, a window setting, a job or snapshot in progress. Reading `plugin.player.state` inside a handler does not: the SDK reads the state again when the plugin starts.
367
+ - **What does not keep it running**: work the player does not see. A `fetch`, a socket, a `setTimeout` or `setInterval`, or a promise the plugin awaits is lost when an idle plugin is stopped, as in Chrome. Set the command's `busy` while its work runs (a busy command keeps the plugin running), or keep what it needs in session storage and pick it up in `activate`.
368
+
369
+ `TestHost.listening` lists the events a plugin really listens to; empty means listeners do not keep it running.
370
+
361
371
  #### The data directory
362
372
 
363
373
  plugin.dataDirectory is a writable directory for the plugin's own files (settings, caches, remembered folders). It needs no capability; the player creates it before it starts the plugin.
@@ -2699,6 +2709,7 @@ The table lists the API whose documentation names a player version, newest last:
2699
2709
  | 0.5.12 | SystemInfo.rendering |
2700
2710
  | 0.5.13 | SystemInfo.renderingReason, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState, Rendering |
2701
2711
  | 0.5.16 | UpscalingReasonCode, UpscalingReason, UpscalingState, VideoUpscaling |
2712
+ | 0.5.20 | VideoShaders |
2702
2713
 
2703
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).
2704
2715
 
@@ -2720,7 +2731,7 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
2720
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 | |
2721
2732
  | `ui.dialog` | Dialogs.confirm, Dialogs.openFile, Dialogs.openFiles, Dialogs.openDirectory, Dialogs.saveFile | — | Show file pickers and confirmations | |
2722
2733
  | `plugins.manage` | Packages.validate, Packages.validateArchive, Packages.setEnabled, Packages.reload, Packages.uninstall | — | Install, uninstall and disable other plugins | yes |
2723
- | `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) | |
2724
2735
  | `processing.audio` | Plugin.audio.filter | `playback.read`, `events` | Process the sound | |
2725
2736
  | `subtitles.read` | Subtitles.state, Subtitles.onChange, Subtitles.refresh | — | Read subtitles | |
2726
2737
  | `subtitles.control` | Subtitles.select, Subtitles.load, Subtitles.remove, Subtitles.setStyle | — | Load and change subtitles | |
@@ -2887,7 +2898,23 @@ RenderSettings; more shaders fail with `invalid`.
2887
2898
 
2888
2899
  | Limit | Value | Meaning |
2889
2900
  | --- | --- | --- |
2890
- | 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`. |
2891
2918
 
2892
2919
  #### Limits documented at their API
2893
2920
 
@@ -3879,7 +3906,7 @@ interface Scope extends Disposable {
3879
3906
  /** Playback state and controls (`playback.read`, `events`, `playback.control`). */
3880
3907
  readonly player: Player;
3881
3908
  /**
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.
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.
3883
3910
  */
3884
3911
  readonly video: {
3885
3912
  /**
@@ -3900,6 +3927,15 @@ interface Scope extends Disposable {
3900
3927
  * @throws Error without a code (throws) A second call in the same plugin while the first handle is not disposed.
3901
3928
  */
3902
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;
3903
3939
  /**
3904
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.
3905
3941
  */
@@ -7445,8 +7481,10 @@ interface SystemInfo {
7445
7481
  * Absent (`undefined`) on players before 0.5.12, which do not report it:
7446
7482
  * the renderer is then unknown, not software.
7447
7483
  *
7448
- * Today the player renders on the GPU on macOS and in software on
7449
- * 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
7450
7488
  * Platforms); test this field
7451
7489
  * rather than SystemInfo.os.
7452
7490
  * @player 0.5.12; older players leave it out (declare `"minPlayerVersion": "0.5.12"` when the plugin relies on it).
@@ -8213,7 +8251,7 @@ What Player.stats reports. Every field is the backend's own value
8213
8251
  ```ts
8214
8252
  interface PlaybackStats {
8215
8253
  /**
8216
- * 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).
8217
8255
  */
8218
8256
  backend: string;
8219
8257
  /** The playback session number. */
@@ -9342,7 +9380,7 @@ interface RenderSettings {
9342
9380
  targetPeak: number;
9343
9381
  /** Per-scene peak estimation (where the GPU supports it). */
9344
9382
  peakDetect: boolean;
9345
- /** 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. */
9346
9384
  shaders: string[];
9347
9385
  /** Motion interpolation, resampled to the display's refresh rate. */
9348
9386
  interpolation: boolean;
@@ -9355,19 +9393,23 @@ The GPU renderer's options, from `plugin.video.render()`
9355
9393
  (`processing.video`). Single owner, like VideoView; the two are
9356
9394
  independent handles.
9357
9395
 
9358
- Platforms: only macOS renders on the GPU. Under software rendering
9359
- (Windows, Linux, headless, fallback) every `set` fails with
9360
- `unsupported`; SystemInfo.rendering tells which renderer draws
9361
- 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
9362
9402
  compute shaders, so `peakDetect` is accepted but has no effect; output
9363
9403
  is always SDR.
9364
9404
 
9365
9405
  GPU renderer options (tone mapping, peak detection, shaders,
9366
- 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);
9367
9408
  under software rendering the handle is `failed` with `unsupported` after
9368
9409
  the player's first answer to `render()` (reported to `onError` too), and so is
9369
9410
  every `set`. Single owner, like VideoView. `set` never throws; the
9370
- 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).
9371
9413
 
9372
9414
  - @capability processing.video, playback.read and events (like VideoFilter)
9373
9415
 
@@ -9385,7 +9427,9 @@ interface RenderOptions extends Disposable {
9385
9427
  * ```
9386
9428
  */
9387
9429
  set(value: Partial<RenderSettings>): void;
9388
- /** `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
+ */
9389
9433
  readonly status: FilterStatus;
9390
9434
  /**
9391
9435
  * The status changed (including later pushes such as shader compile errors).
@@ -9411,7 +9455,8 @@ On a GPU-rendering player HDR video is tone-mapped with BT.2390 for a
9411
9455
  250-nit display; under software rendering the plugin hides its HDR menu.
9412
9456
  `set` merges the values into the plugin's render options and the
9413
9457
  player reports the result through `onStatus`: `failed` with
9414
- `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).
9415
9460
  ```ts
9416
9461
  const render = plugin.video.render();
9417
9462
  render.onStatus((s) => {
@@ -9420,6 +9465,127 @@ render.onStatus((s) => {
9420
9465
  render.set({ toneMapping: "bt.2390", targetPeak: 250 }); // nits
9421
9466
  ```
9422
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
+
9423
9589
  ### UpscalingReasonCode
9424
9590
 
9425
9591
  Why the GPU's video upscaler does not work, as a stable kebab-case code.
@@ -10735,6 +10901,10 @@ interface PluginInfo {
10735
10901
  * The manifest's display texts in the interface language, translated with that package's catalog; absent from players before 0.4.3 (read `manifest.json` there).
10736
10902
  */
10737
10903
  display?: PluginDisplay;
10904
+ /**
10905
+ * For `state: "running"`: `"running"` while its process runs, `"idle"` while the player stopped it because nothing used it (lazy activation: it starts again on its next use, its menu items, keys, settings pages, resolvers and drop targets stay). Absent in other states and from players before 0.5.18. A change between the two alone does not fire Packages.onChange.
10906
+ */
10907
+ process?: "running" | "idle";
10738
10908
  }
10739
10909
  ```
10740
10910
 
@@ -11835,10 +12005,10 @@ RenderingReason.message for the rest. The codes of player 0.5.13:
11835
12005
 
11836
12006
  | Code | Meaning |
11837
12007
  | --- | --- |
11838
- | `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). |
11839
12009
  | `pending` | GPU rendering is starting. |
11840
12010
  | `headless` | The player runs without a user interface. |
11841
- | `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`). |
11842
12012
  | `env` | The environment chose it (`VESTRA_RENDER`, `VESTRA_SOFTWARE_RENDER`). |
11843
12013
  | `preference` | The user chose software rendering (Rendering.setPreference); GPU chosen and available reports `default`. |
11844
12014
  | `denylist` | The graphics driver is on the player's denylist. |
@@ -12203,6 +12373,28 @@ type VideoNode =
12203
12373
  kind: "frameBlend";
12204
12374
  /** Output frame rate, integer 24–120. */
12205
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";
12206
12398
  };
12207
12399
  ```
12208
12400
 
@@ -12224,6 +12416,27 @@ const chain: VideoNode[] = [
12224
12416
  plugin.video.filter("picture").set(chain);
12225
12417
  ```
12226
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
+
12227
12440
  ### AudioNode
12228
12441
 
12229
12442
  One node of an AudioFilter chain, by `kind`. Values outside the
@@ -12796,6 +13009,10 @@ class TestHost implements Transport {
12796
13009
  readonly calls: Call[];
12797
13010
  /** The host events the plugin subscribed to (needs `events` etc.). */
12798
13011
  readonly subscriptions: Set<string>;
13012
+ /**
13013
+ * The subscribed events the plugin really listens to: an `onChange` and the like, or a binding that reads the state. The SDK's own copies of the state (what keeps `plugin.player.state` current) are left out. Empty means the player may stop the plugin while nothing uses it (unless it holds something else: a window, a panel, a filter…).
13014
+ */
13015
+ readonly listening: Set<string>;
12799
13016
  /**
12800
13017
  * The player's source facts (phase, position, duration, paused, rate, audio tracks, loop, ...). Wire-level shape; change it with TestHost.setSource, which also broadcasts it.
12801
13018
  */
@@ -13376,6 +13593,39 @@ class TestHost implements Transport {
13376
13593
  * ```
13377
13594
  */
13378
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;
13379
13629
  /**
13380
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.
13381
13631
  * @example Check the filter chain the plugin applied
@@ -13637,9 +13887,9 @@ class TestHost implements Transport {
13637
13887
  * import activate from "../main.ts";
13638
13888
  *
13639
13889
  * const host = new TestHost();
13640
- * host.videoRender.gpu = false; // software draws now (Windows)
13890
+ * host.videoRender.gpu = false; // software draws now (Linux)
13641
13891
  * host.rendering.startedWith = "software";
13642
- * 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" };
13643
13893
  * host.rendering.gpu = { available: false, reason: host.rendering.reason };
13644
13894
  * const { stop } = await startPlugin(activate, {
13645
13895
  * directory: new URL("..", import.meta.url),
@@ -13647,7 +13897,7 @@ class TestHost implements Transport {
13647
13897
  * });
13648
13898
  * const item = host.command("gpu-rendering");
13649
13899
  * assert.equal(item.state.enabled, false);
13650
- * 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");
13651
13901
  * await stop();
13652
13902
  * ```
13653
13903
  */
@@ -15082,6 +15332,49 @@ const IMAGE_LIMITS: Readonly<{
15082
15332
  }>;
15083
15333
  ```
15084
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
+
15085
15378
  ### HostError
15086
15379
 
15087
15380
  A definite refusal from the player (or the plugin runner), with a stable
@@ -15341,7 +15634,7 @@ What Player.stats reports. Every field is the backend's own value
15341
15634
  ```ts
15342
15635
  interface PlaybackStats {
15343
15636
  /**
15344
- * 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).
15345
15638
  */
15346
15639
  backend: string;
15347
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.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.
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: L2636–2706.
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: L2707–2767.
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: L2077–2265; TestHost: L12744.
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,39 +108,39 @@ 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 L3770.
111
+ - Others: PluginError, llms-full.txt L3797.
112
112
 
113
113
  ## Reference
114
114
 
115
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/).
116
116
  - Tutorials: Getting started L9–237
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
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
+ - 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
+ - Guidelines: Interface guidelines L2441–2548; Menu guidelines L2549–2645
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 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.
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. 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
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.37",
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": {