@vestra-player/sdk 1.5.14 → 1.5.16
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 +24 -0
- package/dist/plugin/packages.js +5 -3
- package/dist/plugin/player.js +8 -0
- package/dist/plugin/testing.d.ts +9 -2
- package/dist/plugin/testing.js +19 -6
- package/dist/plugin/types.d.ts +78 -14
- package/dist/subtitles.d.ts +5 -1
- package/dist/wire.d.ts +13 -1
- package/dist/wire.js +4 -0
- package/llms-full.txt +142 -22
- package/llms.txt +23 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.16
|
|
4
|
+
|
|
5
|
+
**Package rollback is deprecated: the player keeps no previous plugin version.** `apiVersion` stays `vestra/1`; code still compiles, and plugins that do not manage other plugins need no change. 1.5.17 removes the deprecated symbols, as 1.5.8 removed `PackageCandidate.trial()` at patch level. Migration: [Migrating off package rollback](docs/how-to/rollback-migration.md).
|
|
6
|
+
|
|
7
|
+
- **Deprecated: `Packages.rollback`** (removed in 1.5.17). The player no longer has `packages.rollback` and answers it with `unsupported` ("unknown package method"); the SDK still forwards the call, so the error reaches the caller. To go back to an older version of a third-party plugin, the user uninstalls it and installs that version's `.vxp`.
|
|
8
|
+
- **Deprecated: `PluginInfo.previousVersion`** and the wire field `PackageInfo.previousVersion` (removed in 1.5.17): always `null`, since an upgrade replaces the package. The SDK reads a missing field as `null`.
|
|
9
|
+
- **Built-in plugins** run in place from the player installation: `PluginInfo.directory` of a built-in is `<install resources>/plugins/<id>` and read-only (plugins write only under `plugin.dataDirectory`); they can be disabled and enabled, not uninstalled (`unsupported`, as before).
|
|
10
|
+
- **`TestHost`** matches the player: `packages.list` and `packages.changed` report `previousVersion: null` for every plugin, an install no longer records the replaced version, and `packages.rollback` rejects with `unsupported` ("unknown package method"). The `previousVersion` field of `host.packages` entries is deprecated and ignored; it stays required (as `PluginInfo.previousVersion` does), so existing test code compiles unchanged.
|
|
11
|
+
- `"Packages.rollback"` stays in `CAPABILITY_INFO["plugins.manage"].apis` until 1.5.17; the Capabilities reference notes the deprecation.
|
|
12
|
+
- Docs: the new how-to *Migrating off package rollback (1.5.16)*; *Publish a plugin* no longer mentions a roll back button, installing an older version from the store or the user's rollback as a safety net; the data directory is "kept across restarts, disabling and upgrades"; `revisionConflict`, `PackageChange` and `PackageChangeStatus` speak of installs and uninstalls (the statuses are unchanged).
|
|
13
|
+
|
|
14
|
+
## 1.5.15
|
|
15
|
+
|
|
16
|
+
**How many frames really reach the screen, and how the video is decoded** (player 0.5.1); every player error code documented; the fullscreen bottom bar's new behaviour. Additive; `apiVersion` stays `vestra/1`, and plugins need no change.
|
|
17
|
+
|
|
18
|
+
- **`PlaybackStats.presentedFps`**: new video frames the player window showed per second over the last second, measured by the player (draws of the main window that put a new picture on screen). `framesShown` counts the frames the video renderer produced, so it cannot show frames lost afterwards, for example a window that draws only every other frame: then `presentedFps` is about half of `video.fps`. `null` when the player cannot measure it (no window, the first second) and with players before 0.5.1.
|
|
19
|
+
- **`PlaybackStats.video.decoding`**: `"hardware"` or `"software"`, from mpv's `hwdec-current` (the method stays in `video.hwdec`, e.g. `"d3d11va-copy"`); `null` before a decoder runs. With players that do not send it, the SDK derives it from `hwdec`.
|
|
20
|
+
- `framesShown` and `framesDropped` say what they count (TSDoc only).
|
|
21
|
+
- **`TestHost`**: `host.stats` takes both fields; `presentedFps` is `null` by default, and `video.decoding` follows `video.hwdec` when a test leaves it out.
|
|
22
|
+
- **`PluginError`**: the table lists the player's remaining codes with their meaning: `activation`, `command`, `input-overflow`, `internal`, `javascript`, `slint`, `owner`, `pending`, `producer`, `resolution`, `revisionConflict`, `source-geometry`, `source-geometry-unsupported`, `source-unavailable` and `unknown-expired`. `pending`, `unknown-expired` and `revisionConflict` are mostly handled inside the SDK, but can reach a plugin.
|
|
23
|
+
- **`SubtitleStyle.offsetMs`**: the range is −600000…600000 ms (±10 minutes), positive shows subtitles later, and outside it the player fails with `invalid`.
|
|
24
|
+
- **The fullscreen bottom bar** (player 0.5.1): it shows only while the pointer is in the bar's own band at the bottom of the screen, at once on entering and hidden 500 ms after leaving; keys and pointer motion elsewhere no longer reveal it, and `Panel.setInteraction` locks keep it. `SlotState.reason` `"idle"`, `Panel.setInteraction` and `Panels.mount` (`"bottom"`) said "hides after 3 seconds without activity".
|
|
25
|
+
- `llms.txt` names the declaration files by path again (`node_modules/@vestra-player/sdk/dist/plugin/types.d.ts` for the API, `dist/plugin/testing.d.ts` for TestHost) instead of `dist/plugin/*.d.ts`.
|
|
26
|
+
|
|
3
27
|
## 1.5.14
|
|
4
28
|
|
|
5
29
|
**The documentation site in seven groups, with generated reference tables.** A documentation release; `apiVersion` stays `vestra/1`, and plugins need no change. Everything a plugin author needs is now on the [documentation site](https://vestrasys.github.io/vestra-player-sdk/) once, in English.
|
package/dist/plugin/packages.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Package validation and
|
|
1
|
+
/** Package validation, install and uninstall (`plugins.manage`). */
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { failure, sleep, uncertain } from "./runtime.js";
|
|
@@ -62,7 +62,7 @@ class ChangeHandle {
|
|
|
62
62
|
this.#accept(await this.#scope.runtime.host.packages.cancel(this.#id));
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
/** Install and
|
|
65
|
+
/** Install and uninstall reuse the request id as the operation id after a lost reply. */
|
|
66
66
|
async function change(scope, submit, id) {
|
|
67
67
|
const host = scope.runtime.host.packages;
|
|
68
68
|
const installed = await host.installed(id);
|
|
@@ -126,6 +126,8 @@ export const packages = (scope) => ({
|
|
|
126
126
|
const checked = await scope.runtime.host.packages.validateArchive(path);
|
|
127
127
|
return candidate(scope, path, checked);
|
|
128
128
|
},
|
|
129
|
+
// Deprecated (removed in 1.5.17): still forwarded, so the player's
|
|
130
|
+
// `unsupported` ("unknown package method") reaches the caller.
|
|
129
131
|
rollback: (id) => change(scope, (requestId, revision) => scope.runtime.host.packages.rollback(id, revision, requestId), id),
|
|
130
132
|
list: async () => (await scope.runtime.host.packages.list()).map(info),
|
|
131
133
|
onChange(listener) {
|
|
@@ -152,7 +154,7 @@ const info = (plugin) => Object.freeze({
|
|
|
152
154
|
error: plugin.error,
|
|
153
155
|
capabilities: [...plugin.capabilities],
|
|
154
156
|
directory: plugin.directory,
|
|
155
|
-
previousVersion: plugin.previousVersion,
|
|
157
|
+
previousVersion: plugin.previousVersion ?? null,
|
|
156
158
|
builtin: plugin.builtin === true,
|
|
157
159
|
source: plugin.source ? Object.freeze({ ...plugin.source }) : null,
|
|
158
160
|
dataDirectory: plugin.dataDirectory ?? null,
|
package/dist/plugin/player.js
CHANGED
|
@@ -81,6 +81,12 @@ function view(raw) {
|
|
|
81
81
|
raws.set(state, raw);
|
|
82
82
|
return state;
|
|
83
83
|
}
|
|
84
|
+
/** `stats().video.decoding` from `hwdec` for players that do not send it. */
|
|
85
|
+
function decoding(hwdec) {
|
|
86
|
+
if (hwdec === null || hwdec === undefined || hwdec === "")
|
|
87
|
+
return null;
|
|
88
|
+
return hwdec === "no" ? "software" : "hardware";
|
|
89
|
+
}
|
|
84
90
|
export const rawOf = (state) => raws.get(state);
|
|
85
91
|
export const sameStamp = (a, b) => a.generation === b.generation && a.intentVersion === b.intentVersion;
|
|
86
92
|
function sourceFailure(error) {
|
|
@@ -474,6 +480,7 @@ export function player(scope) {
|
|
|
474
480
|
session: raw.session,
|
|
475
481
|
framesShown: raw.framesShown,
|
|
476
482
|
framesDropped: raw.framesDropped,
|
|
483
|
+
presentedFps: n(raw.presentedFps),
|
|
477
484
|
video: raw.video && {
|
|
478
485
|
codec: raw.video.codec,
|
|
479
486
|
width: raw.video.width,
|
|
@@ -487,6 +494,7 @@ export function player(scope) {
|
|
|
487
494
|
displayFps: n(raw.video.displayFps),
|
|
488
495
|
bitrate: n(raw.video.bitrate),
|
|
489
496
|
hwdec: n(raw.video.hwdec),
|
|
497
|
+
decoding: raw.video.decoding ?? decoding(raw.video.hwdec),
|
|
490
498
|
transfer: n(raw.video.transfer),
|
|
491
499
|
},
|
|
492
500
|
audio: raw.audio && {
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -1311,7 +1311,9 @@ export declare class TestHost implements Transport {
|
|
|
1311
1311
|
/**
|
|
1312
1312
|
* What `packages.list` reports; `setEnabled`, `uninstall` and `install`
|
|
1313
1313
|
* update it. `builtin`, `source` and `dataDirectory` are optional here
|
|
1314
|
-
* (default `false`, `null`, `null`).
|
|
1314
|
+
* (default `false`, `null`, `null`). Like the player, the host lists every
|
|
1315
|
+
* plugin with `previousVersion: null` and answers `packages.rollback` with
|
|
1316
|
+
* `unsupported` (since 1.5.16).
|
|
1315
1317
|
*/
|
|
1316
1318
|
packages: {
|
|
1317
1319
|
/** The plugin's manifest id. */
|
|
@@ -1328,7 +1330,12 @@ export declare class TestHost implements Transport {
|
|
|
1328
1330
|
capabilities: string[];
|
|
1329
1331
|
/** The package directory. */
|
|
1330
1332
|
directory: string;
|
|
1331
|
-
/**
|
|
1333
|
+
/**
|
|
1334
|
+
* Ignored: `packages.list` always reports `null`. Still required in
|
|
1335
|
+
* 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling.
|
|
1336
|
+
*
|
|
1337
|
+
* @deprecated Removed in 1.5.17 with `PluginInfo.previousVersion`.
|
|
1338
|
+
*/
|
|
1332
1339
|
previousVersion: string | null;
|
|
1333
1340
|
/** Shipped with the player: `uninstall` and `install` are `unsupported`. */
|
|
1334
1341
|
builtin?: boolean;
|
package/dist/plugin/testing.js
CHANGED
|
@@ -479,6 +479,7 @@ export class TestHost {
|
|
|
479
479
|
session: 0,
|
|
480
480
|
framesShown: 0,
|
|
481
481
|
framesDropped: 0,
|
|
482
|
+
presentedFps: null,
|
|
482
483
|
video: null,
|
|
483
484
|
audio: null,
|
|
484
485
|
avSyncMs: null,
|
|
@@ -1861,7 +1862,9 @@ export class TestHost {
|
|
|
1861
1862
|
/**
|
|
1862
1863
|
* What `packages.list` reports; `setEnabled`, `uninstall` and `install`
|
|
1863
1864
|
* update it. `builtin`, `source` and `dataDirectory` are optional here
|
|
1864
|
-
* (default `false`, `null`, `null`).
|
|
1865
|
+
* (default `false`, `null`, `null`). Like the player, the host lists every
|
|
1866
|
+
* plugin with `previousVersion: null` and answers `packages.rollback` with
|
|
1867
|
+
* `unsupported` (since 1.5.16).
|
|
1865
1868
|
*/
|
|
1866
1869
|
packages = [];
|
|
1867
1870
|
/** Every `packages.setEnabled` / `uninstall` / `install`, newest last. */
|
|
@@ -1918,7 +1921,14 @@ export class TestHost {
|
|
|
1918
1921
|
*/
|
|
1919
1922
|
setPackages(list) {
|
|
1920
1923
|
this.packages = list;
|
|
1921
|
-
this.emit("packages.changed",
|
|
1924
|
+
this.emit("packages.changed", this.#listed());
|
|
1925
|
+
}
|
|
1926
|
+
/** `packages.list` as the player sends it: no previous version is kept. */
|
|
1927
|
+
#listed() {
|
|
1928
|
+
return this.packages.map((entry) => ({
|
|
1929
|
+
...structuredClone(entry),
|
|
1930
|
+
previousVersion: null,
|
|
1931
|
+
}));
|
|
1922
1932
|
}
|
|
1923
1933
|
#split(key) {
|
|
1924
1934
|
const at = key.indexOf("/");
|
|
@@ -3152,7 +3162,7 @@ export class TestHost {
|
|
|
3152
3162
|
this.bindings.overlay.delete(`${p.plugin}/${p.command}`);
|
|
3153
3163
|
this.emit("bindings.changed", null);
|
|
3154
3164
|
},
|
|
3155
|
-
"packages.list": () =>
|
|
3165
|
+
"packages.list": () => this.#listed(),
|
|
3156
3166
|
"packages.setEnabled": (p) => {
|
|
3157
3167
|
const id = String(p.id);
|
|
3158
3168
|
if (id === this.manifestId && p.enabled === false)
|
|
@@ -3204,9 +3214,7 @@ export class TestHost {
|
|
|
3204
3214
|
error: null,
|
|
3205
3215
|
capabilities: [...manifest.capabilities],
|
|
3206
3216
|
directory: `/test-host/packages/${manifest.id}`,
|
|
3207
|
-
previousVersion:
|
|
3208
|
-
? entry.version
|
|
3209
|
-
: null,
|
|
3217
|
+
previousVersion: null,
|
|
3210
3218
|
builtin: false,
|
|
3211
3219
|
source,
|
|
3212
3220
|
dataDirectory: entry?.dataDirectory ?? null,
|
|
@@ -3234,6 +3242,11 @@ export class TestHost {
|
|
|
3234
3242
|
this.setPackages(this.packages.filter((each) => each.id !== id));
|
|
3235
3243
|
return { operationId: String(p.requestId), status: "committed" };
|
|
3236
3244
|
},
|
|
3245
|
+
// The player no longer keeps a previous version (SDK 1.5.16): the method
|
|
3246
|
+
// is gone and fails like any unknown package method.
|
|
3247
|
+
"packages.rollback": () => {
|
|
3248
|
+
throw new HostError("unsupported", "unknown package method");
|
|
3249
|
+
},
|
|
3237
3250
|
"ui.markdownScan": (p) => {
|
|
3238
3251
|
const text = String(p.text);
|
|
3239
3252
|
const images = [
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -81,6 +81,19 @@ export interface Disposable {
|
|
|
81
81
|
* | `host` | Any other failure inside the player; `message` is the player's reason. |
|
|
82
82
|
* | `disabled` | The plugin is being stopped; pending requests fail with it. |
|
|
83
83
|
* | `unknown`, `rejected`, `expired` | After a transport problem the SDK could not confirm the outcome (does not happen in normal operation). |
|
|
84
|
+
* | `activation` | The plugin opened a tool window while its activation was still pending and the player could not track it. |
|
|
85
|
+
* | `command` | The player could not run or update a command it no longer knows for this plugin (removed meanwhile, or another plugin's). |
|
|
86
|
+
* | `input-overflow` | `openStream` input: the player's queue of physical key transitions was full; the input was revoked and its keys released. |
|
|
87
|
+
* | `internal` | An internal step of the player failed (keeping an operation's result, a subtitle or media job); `message` has the reason. |
|
|
88
|
+
* | `javascript`, `slint` | {@link Packages.validate}: the package's entry module does not pass `node --check` / one of its Slint files does not compile; `message` has the diagnostics. |
|
|
89
|
+
* | `owner` | A resolver answered a request that belongs to another plugin's resolver. |
|
|
90
|
+
* | `pending` | An operation with the same token is still running in the player. The SDK waits and asks again, so it rarely reaches a plugin. |
|
|
91
|
+
* | `producer` | `openStream`: the player refused or lost the generated stream; `message` has the reason. |
|
|
92
|
+
* | `resolution` | A file or web resolver's result could not be opened; `message` has the reason. |
|
|
93
|
+
* | `revisionConflict` | {@link Packages}: the installed revision changed since it was read (another install or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
|
|
94
|
+
* | `source-geometry`, `source-geometry-unsupported` | The source's picture size or rotation could not be applied: invalid decoder dimensions / a rotation that is not a multiple of 90°. |
|
|
95
|
+
* | `source-unavailable` | The source is not ready (still loading, or its time or native binding is not available yet). |
|
|
96
|
+
* | `unknown-expired` | The player no longer knows the operation or snapshot (it expired or was released). The SDK maps it to `unknownExpired` results where it can; do not resubmit the operation blindly. |
|
|
84
97
|
*
|
|
85
98
|
* Errors thrown by handlers the SDK invokes go to {@link Scope.onError}. A
|
|
86
99
|
* rejected promise you forgot to `await` is only logged; it never crashes
|
|
@@ -323,7 +336,7 @@ export interface Plugin extends Scope {
|
|
|
323
336
|
* A writable directory for this plugin's own files (settings, caches,
|
|
324
337
|
* remembered folders), created by the player before it starts the plugin.
|
|
325
338
|
* Each installation has its own directory (player 0.2.0): it is kept across
|
|
326
|
-
* restarts, disabling
|
|
339
|
+
* restarts, disabling and upgrades, and a reinstall of the same
|
|
327
340
|
* id from a different source gets a new, empty one. It is deleted only by
|
|
328
341
|
* `packages.uninstall(id, { removeData: true })`. No capability.
|
|
329
342
|
*
|
|
@@ -1345,8 +1358,10 @@ export interface SlotState {
|
|
|
1345
1358
|
* - `fullscreen`: the sidebar's fullscreen overlay is collapsed and pops
|
|
1346
1359
|
* out when the pointer reaches the right edge; treat it as "on" for a
|
|
1347
1360
|
* toggle button;
|
|
1348
|
-
* - `idle`: the bottom bar in fullscreen
|
|
1349
|
-
*
|
|
1361
|
+
* - `idle`: the bottom bar in fullscreen while the pointer is outside the
|
|
1362
|
+
* bar's own band at the bottom of the screen (player 0.5.1: keys and
|
|
1363
|
+
* pointer motion elsewhere do not bring it back; before 0.5.1 it hid after
|
|
1364
|
+
* 3 seconds without activity).
|
|
1350
1365
|
*/
|
|
1351
1366
|
reason: "userHidden" | "space" | "fullscreen" | "idle" | null;
|
|
1352
1367
|
/** The sidebar floats in its own window; always `false` for the bottom bar. */
|
|
@@ -1783,8 +1798,11 @@ export interface Panel extends View, Disposable {
|
|
|
1783
1798
|
* Keep the panel on screen while the user works in it.
|
|
1784
1799
|
*
|
|
1785
1800
|
* @remarks
|
|
1786
|
-
*
|
|
1787
|
-
* the
|
|
1801
|
+
* In fullscreen the bottom bar shows only while the pointer is in the bar's
|
|
1802
|
+
* own band at the bottom of the screen: at once when the pointer enters it,
|
|
1803
|
+
* hidden 500 ms after it leaves (player 0.5.1; keys and pointer motion
|
|
1804
|
+
* elsewhere do not reveal it), and the fullscreen sidebar overlay collapses
|
|
1805
|
+
* when the pointer leaves it. A lock keeps the bar shown. Set a
|
|
1788
1806
|
* lock while a text field has focus, a drag is in progress or a popup is
|
|
1789
1807
|
* open, and clear it afterwards. Locks are merged with the earlier ones;
|
|
1790
1808
|
* the last value of each wins. The player already locks a panel while the
|
|
@@ -2696,7 +2714,9 @@ export interface Panels {
|
|
|
2696
2714
|
* - `"bottom"`: under the video, as wide as the video column, as tall as the
|
|
2697
2715
|
* component's preferred height. It may draw above its slot into the video
|
|
2698
2716
|
* area (negative `y`); the player does not clip it. In fullscreen it
|
|
2699
|
-
* floats over the video and
|
|
2717
|
+
* floats over the video and shows only while the pointer is in its band
|
|
2718
|
+
* at the bottom of the screen (hidden 500 ms after the pointer leaves;
|
|
2719
|
+
* {@link Panel.setInteraction} locks keep it).
|
|
2700
2720
|
* - `"sidebar"`: right of the video, the window's full height, as wide as the
|
|
2701
2721
|
* user drags it (at least 317). The user can detach it into a floating
|
|
2702
2722
|
* window titled `title`. Several plugins may mount here (player 0.4.x
|
|
@@ -3491,10 +3511,27 @@ export interface PlaybackStats {
|
|
|
3491
3511
|
backend: string;
|
|
3492
3512
|
/** The playback session number. */
|
|
3493
3513
|
session: number;
|
|
3494
|
-
/**
|
|
3514
|
+
/**
|
|
3515
|
+
* Frames the video renderer produced. Not all of them reach the screen: a
|
|
3516
|
+
* busy window shows only the newest one, so compare
|
|
3517
|
+
* {@link PlaybackStats.presentedFps | presentedFps}.
|
|
3518
|
+
*/
|
|
3495
3519
|
framesShown: number;
|
|
3496
|
-
/** Frames dropped, `null` when unknown. */
|
|
3520
|
+
/** Frames dropped by the backend (mpv), `null` when unknown. */
|
|
3497
3521
|
framesDropped: number | null;
|
|
3522
|
+
/**
|
|
3523
|
+
* New video frames the player window showed per second over the last
|
|
3524
|
+
* second, measured by the player itself: draws of the main window that put
|
|
3525
|
+
* a new picture on screen. Lower than `video.fps` when frames are lost
|
|
3526
|
+
* after decoding and rendering (the window could not keep up with the
|
|
3527
|
+
* source, or the display refreshes slower); `0` while paused. `null` when
|
|
3528
|
+
* the player cannot measure it (no window, the first second, players
|
|
3529
|
+
* before 0.5.1).
|
|
3530
|
+
*
|
|
3531
|
+
* @player 0.5.1
|
|
3532
|
+
* @since 1.5.15
|
|
3533
|
+
*/
|
|
3534
|
+
presentedFps: number | null;
|
|
3498
3535
|
/** Every field is the backend's own value, `null` when it has none. */
|
|
3499
3536
|
video: {
|
|
3500
3537
|
/** Codec name. */
|
|
@@ -3519,8 +3556,15 @@ export interface PlaybackStats {
|
|
|
3519
3556
|
displayFps: number | null;
|
|
3520
3557
|
/** Bits per second, updated per keyframe. */
|
|
3521
3558
|
bitrate: number | null;
|
|
3522
|
-
/** Hardware decoder in use, e.g. "videotoolbox"; "no" = software decoding. */
|
|
3559
|
+
/** Hardware decoder in use, e.g. "videotoolbox", "d3d11va-copy"; "no" = software decoding. */
|
|
3523
3560
|
hwdec: string | null;
|
|
3561
|
+
/**
|
|
3562
|
+
* How the video is decoded, from `hwdec`: `"hardware"` (the method is in
|
|
3563
|
+
* `hwdec`) or `"software"`; `null` before a decoder runs.
|
|
3564
|
+
*
|
|
3565
|
+
* @since 1.5.15 (with older players the SDK derives it from `hwdec`)
|
|
3566
|
+
*/
|
|
3567
|
+
decoding: "hardware" | "software" | null;
|
|
3524
3568
|
/** Transfer characteristic as mpv names it, e.g. "bt.1886", "pq", "hlg". */
|
|
3525
3569
|
transfer: string | null;
|
|
3526
3570
|
} | null;
|
|
@@ -4649,10 +4693,10 @@ export interface PackageCandidate {
|
|
|
4649
4693
|
*/
|
|
4650
4694
|
install(options?: InstallOptions): Promise<PackageChange>;
|
|
4651
4695
|
}
|
|
4652
|
-
/** The progress of an install
|
|
4696
|
+
/** The progress of an install or uninstall. */
|
|
4653
4697
|
export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
|
|
4654
4698
|
/**
|
|
4655
|
-
* An install
|
|
4699
|
+
* An install or uninstall in progress. The SDK never submits a
|
|
4656
4700
|
* change twice: after a lost reply it only queries the same request.
|
|
4657
4701
|
*/
|
|
4658
4702
|
export interface PackageChange {
|
|
@@ -4681,9 +4725,21 @@ export interface PluginInfo {
|
|
|
4681
4725
|
error: string | null;
|
|
4682
4726
|
/** Declared capabilities. */
|
|
4683
4727
|
capabilities: string[];
|
|
4684
|
-
/**
|
|
4728
|
+
/**
|
|
4729
|
+
* The package directory (read its `manifest.json` for display fields).
|
|
4730
|
+
* A built-in's directory is inside the player installation
|
|
4731
|
+
* (`<install resources>/plugins/<id>`) and read-only: a plugin writes only
|
|
4732
|
+
* under its own {@link Plugin.dataDirectory}.
|
|
4733
|
+
*/
|
|
4685
4734
|
directory: string;
|
|
4686
|
-
/**
|
|
4735
|
+
/**
|
|
4736
|
+
* Always `null`: the player no longer keeps a previous version, and an
|
|
4737
|
+
* upgrade replaces the package.
|
|
4738
|
+
*
|
|
4739
|
+
* @deprecated Removed in 1.5.17. The player no longer keeps a previous
|
|
4740
|
+
* version, so this is always `null`. See
|
|
4741
|
+
* [Migrating off package rollback](../../docs/how-to/rollback-migration.md).
|
|
4742
|
+
*/
|
|
4687
4743
|
previousVersion: string | null;
|
|
4688
4744
|
/**
|
|
4689
4745
|
* Shipped with the player: it can be disabled, not uninstalled or
|
|
@@ -4783,7 +4839,15 @@ export interface Packages {
|
|
|
4783
4839
|
* fails with `reserved`, as in {@link Packages.validate}.
|
|
4784
4840
|
*/
|
|
4785
4841
|
validateArchive(file: string): Promise<PackageCandidate>;
|
|
4786
|
-
/**
|
|
4842
|
+
/**
|
|
4843
|
+
* Formerly restored a plugin's previous version (`plugins.manage`).
|
|
4844
|
+
*
|
|
4845
|
+
* @deprecated Removed in 1.5.17. The player no longer keeps a previous
|
|
4846
|
+
* version, so this always fails with `unsupported`. To go back to an older
|
|
4847
|
+
* version of a third-party plugin, the user uninstalls it and installs that
|
|
4848
|
+
* version's `.vxp`. See
|
|
4849
|
+
* [Migrating off package rollback](../../docs/how-to/rollback-migration.md).
|
|
4850
|
+
*/
|
|
4787
4851
|
rollback(id: string): Promise<PackageChange>;
|
|
4788
4852
|
/** Every plugin: running, installed, and this run's development loads. No capability. */
|
|
4789
4853
|
list(): Promise<PluginInfo[]>;
|
package/dist/subtitles.d.ts
CHANGED
|
@@ -45,7 +45,11 @@ export type SubtitleStyle = {
|
|
|
45
45
|
outline: number;
|
|
46
46
|
/** Bottom margin 2–15 % of the video height. */
|
|
47
47
|
marginPercent: number;
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Timing offset in milliseconds, −600000…600000 (±10 minutes); positive
|
|
50
|
+
* shows the subtitles later. Outside the range the player refuses the
|
|
51
|
+
* style with `invalid`.
|
|
52
|
+
*/
|
|
49
53
|
offsetMs: number;
|
|
50
54
|
/** Bold text; default false. */
|
|
51
55
|
bold?: boolean;
|
package/dist/wire.d.ts
CHANGED
|
@@ -102,6 +102,11 @@ export interface PackageInfo {
|
|
|
102
102
|
error: string | null;
|
|
103
103
|
capabilities: string[];
|
|
104
104
|
directory: string;
|
|
105
|
+
/**
|
|
106
|
+
* Always `null` from players that ship with SDK 1.5.16 or later.
|
|
107
|
+
*
|
|
108
|
+
* @deprecated Removed in 1.5.17: the player keeps no previous version.
|
|
109
|
+
*/
|
|
105
110
|
previousVersion: string | null;
|
|
106
111
|
/** Player 0.2.0; absent from older players. */
|
|
107
112
|
builtin?: boolean;
|
|
@@ -222,6 +227,8 @@ export interface PlaybackStats {
|
|
|
222
227
|
session: number;
|
|
223
228
|
framesShown: number;
|
|
224
229
|
framesDropped: number | null;
|
|
230
|
+
/** Player 0.5.1 and newer; the SDK fills in `null`. */
|
|
231
|
+
presentedFps?: number | null;
|
|
225
232
|
video: {
|
|
226
233
|
codec: string | null;
|
|
227
234
|
width: number | null;
|
|
@@ -235,6 +242,7 @@ export interface PlaybackStats {
|
|
|
235
242
|
displayFps?: number | null;
|
|
236
243
|
bitrate?: number | null;
|
|
237
244
|
hwdec?: string | null;
|
|
245
|
+
decoding?: "hardware" | "software" | null;
|
|
238
246
|
transfer?: string | null;
|
|
239
247
|
} | null;
|
|
240
248
|
audio: {
|
|
@@ -564,6 +572,10 @@ export declare function createWire(transport: Transport): {
|
|
|
564
572
|
source?: WireSource;
|
|
565
573
|
replace?: boolean;
|
|
566
574
|
}) => Promise<PackageOperation>;
|
|
575
|
+
/**
|
|
576
|
+
* @deprecated Removed in 1.5.17. The player no longer has this method and
|
|
577
|
+
* rejects it with `unsupported` ("unknown package method").
|
|
578
|
+
*/
|
|
567
579
|
rollback: (id: string, expectedRevision: number, requestId: string) => Promise<PackageOperation>;
|
|
568
580
|
operation: (operationId: string) => Promise<PackageOperation>;
|
|
569
581
|
cancel: (operationId: string) => Promise<PackageOperation>;
|
|
@@ -590,7 +602,7 @@ export declare function createWire(transport: Transport): {
|
|
|
590
602
|
sampleTime?: number;
|
|
591
603
|
} | {
|
|
592
604
|
kind: "rawJpeg" | "processedJpeg";
|
|
593
|
-
sampleTime
|
|
605
|
+
sampleTime? /** The path's keys (player 0.4.3). */: number;
|
|
594
606
|
quality?: number;
|
|
595
607
|
} | {
|
|
596
608
|
kind: "rawMp4" | "processedMp4";
|
package/dist/wire.js
CHANGED
|
@@ -278,6 +278,10 @@ export function createWire(transport) {
|
|
|
278
278
|
...(options.source === undefined ? {} : { source: options.source }),
|
|
279
279
|
...(options.replace ? { replace: true } : {}),
|
|
280
280
|
}),
|
|
281
|
+
/**
|
|
282
|
+
* @deprecated Removed in 1.5.17. The player no longer has this method and
|
|
283
|
+
* rejects it with `unsupported` ("unknown package method").
|
|
284
|
+
*/
|
|
281
285
|
rollback: (id, expectedRevision, requestId) => transport.request("packages.rollback", {
|
|
282
286
|
id,
|
|
283
287
|
expectedRevision,
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.16: 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
|
|
|
@@ -319,7 +319,7 @@ So `onDispose` cleanup must be quick and must not rely on the player. Media the
|
|
|
319
319
|
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.
|
|
320
320
|
|
|
321
321
|
- **One directory per installation.** Each fresh installation of a plugin gets its own directory (player 0.2.0). Installing the same id again from the same source is an upgrade and keeps the directory. Installing the same id from a different source, and confirming the replacement, gives the new plugin a new, empty directory; the old one is kept and reconnected if the user goes back to the first source later. A development load (`--plugin`) has a directory of its own.
|
|
322
|
-
- **Kept** when the plugin is disabled, reloaded
|
|
322
|
+
- **Kept** when the plugin is disabled, reloaded or upgraded. Uninstalling deletes it only when the user asks for that (uninstall with `removeData: true`); otherwise reinstalling from the same source reconnects it.
|
|
323
323
|
- **The path's layout is not part of the API.** Do not derive another plugin's directory from yours; a plugin manager reads it from PluginInfo.dataDirectory.
|
|
324
324
|
- **Write atomically.** Write to a temporary name and rename it, so a player that quits mid-write never leaves half a file; fall back to defaults when a file is missing or unreadable. For JSON settings use plugin.storage.json, which already does this (defaults, debounced atomic writes, a corrupt file moved aside).
|
|
325
325
|
- Reading the property throws `unavailable` when the player could not provide a directory.
|
|
@@ -488,6 +488,7 @@ Reading it needs `playback.read`; following it needs `events` too:
|
|
|
488
488
|
- The SDK reads the state once before `activate` and, with `events`, keeps `player.state` current and calls onChange listeners after every change. The state **never goes backwards**: a late event that is older than what the plugin already has is dropped.
|
|
489
489
|
- Without `events`, `player.state` stays as it was first read until you call refresh().
|
|
490
490
|
- Some facts are read on demand instead of pushed, because they are large or rarely needed: chapters(), stats() (codecs, frame counts, decoder, bitrates), audioDevices().
|
|
491
|
+
- In `stats()`, the frame counts come from two places. `framesShown` and `framesDropped` are the video renderer's (mpv's) counts. `presentedFps` is what the player window really showed: new frames per second over the last second. When it stays well below `video.fps` while playing, frames are lost after rendering (the window cannot keep up, or the display refreshes slower). `video.decoding` says whether the video is decoded in `"hardware"` (the method in `video.hwdec`) or `"software"`.
|
|
491
492
|
|
|
492
493
|
Because the state is a snapshot, a plugin can keep one and use it later as the condition for a control.
|
|
493
494
|
|
|
@@ -1752,9 +1753,9 @@ Every member of `TestHost` is in the @vestra-player/sdk/testing reference.
|
|
|
1752
1753
|
|
|
1753
1754
|
## How-to: Publish a plugin
|
|
1754
1755
|
|
|
1755
|
-
**Goal:** your plugin appears in the player's plugin store, where users install, update and
|
|
1756
|
+
**Goal:** your plugin appears in the player's plugin store, where users install, update and uninstall it.
|
|
1756
1757
|
|
|
1757
|
-
The store reads the official plugin repository, [vestrasys/vestra-player-plugins](https://github.com/vestrasys/vestra-player-plugins). Every change to it is a pull request to its `main` branch, and **a merge is a release**: there are no beta channels, and CI
|
|
1758
|
+
The store reads the official plugin repository, [vestrasys/vestra-player-plugins](https://github.com/vestrasys/vestra-player-plugins). Every change to it is a pull request to its `main` branch, and **a merge is a release**: there are no beta channels, and CI and revocation are the safety net. Every plugin in it is a third-party plugin; only the plugins built into the player are official. The repository's [README](https://github.com/vestrasys/vestra-player-plugins#contributing-a-plugin) and [CONTRIBUTING](https://github.com/vestrasys/vestra-player-plugins/blob/main/CONTRIBUTING.md) hold the full rules; this page is the author's path through them.
|
|
1758
1759
|
|
|
1759
1760
|
#### Two ways in
|
|
1760
1761
|
|
|
@@ -1835,10 +1836,84 @@ After the merge, the repository's publish workflow builds the package, releases
|
|
|
1835
1836
|
The store shows the newest version's manifest fields, so what you write there is the plugin's page:
|
|
1836
1837
|
|
|
1837
1838
|
- **The list**: name and version, author and repository, the punchline. Search covers the name, id, author, punchline and tags; the list can be filtered by tag and sorted by name or by the date of the newest version. There are no download counts.
|
|
1838
|
-
- **The plugin's page**: the icon, name, author, punchline and the buttons (install, update,
|
|
1839
|
+
- **The plugin's page**: the icon, name, author, punchline and the buttons (install, update, uninstall). Three tabs show the README, the changelog and the version history (version, release date, `minPlayerVersion`, and whether the version is current, incompatible or revoked). The player keeps no previous version, and the store has no action to install an older one: a user who wants an older version uninstalls the plugin and installs that version's `.vxp` (Migrating off package rollback). A side column shows the version, the date, the license, a link to `sourceUrl` and the capabilities in plain words, the risky ones highlighted (the wording is the "Shown in the store as" column of Capabilities), with a fixed note that capabilities limit only the player API, not file or network access.
|
|
1839
1840
|
- **Before installing**, the user confirms a dialog that lists the capabilities; an update that adds capabilities lists the new ones. A plugin whose every version needs a newer player shows "needs Vestra Player x.y.z or later" instead of an install button.
|
|
1840
1841
|
- **The README** is rendered as Markdown; relative links and images resolve against the README's own address, and remote images are shown for the official repository.
|
|
1841
1842
|
|
|
1843
|
+
## How-to: Migrating off package rollback (1.5.16)
|
|
1844
|
+
|
|
1845
|
+
For plugins that manage other plugins (`plugins.manage`): stores, plugin managers, anything that calls packages.rollback or reads PluginInfo.previousVersion. A plugin that only uses its own API needs no change.
|
|
1846
|
+
|
|
1847
|
+
#### What changed
|
|
1848
|
+
|
|
1849
|
+
The player no longer keeps a previous version of a plugin. An upgrade replaces the package, so there is nothing to roll back to:
|
|
1850
|
+
|
|
1851
|
+
- `packages.rollback(id)` always fails with `unsupported` (the player answers "unknown package method").
|
|
1852
|
+
- Every entry of `packages.list()` and `packages.onChange` has `previousVersion: null`.
|
|
1853
|
+
- Built-in plugins (`builtin: true`) run in place from the player installation. They can be disabled and enabled, never uninstalled or replaced; `uninstall` fails with `unsupported`. A built-in's `directory` is inside the installation (`<install resources>/plugins/<id>`) and read-only.
|
|
1854
|
+
- Third-party plugins can still be uninstalled.
|
|
1855
|
+
|
|
1856
|
+
Why: built-ins now run from the read-only installation instead of a copy, and an upgrade replaces the package instead of keeping the old one beside it. Going back to an older version becomes an ordinary uninstall and install that the user starts.
|
|
1857
|
+
|
|
1858
|
+
`TestHost` behaves the same way since 1.5.16, so tests show it too.
|
|
1859
|
+
|
|
1860
|
+
#### 1. Remove rollback calls and `previousVersion` checks
|
|
1861
|
+
|
|
1862
|
+
Before:
|
|
1863
|
+
|
|
1864
|
+
```ts nocheck
|
|
1865
|
+
if (info.previousVersion !== null) showButton("Roll back", () => plugin.packages.rollback(info.id));
|
|
1866
|
+
```
|
|
1867
|
+
|
|
1868
|
+
After: delete both. There is no replacement call; nothing on the player can restore an earlier package. Remove the button, menu item or command, and any state, text or test that depends on `previousVersion`.
|
|
1869
|
+
|
|
1870
|
+
#### 2. Going back to an older version
|
|
1871
|
+
|
|
1872
|
+
Managing plugins has exactly three operations: **install**, **uninstall** and **disable/enable**. There is no rollback, no "install older version" and no "Undo install".
|
|
1873
|
+
|
|
1874
|
+
To go back to an older version, the user uninstalls the plugin and installs the older version's `.vxp` file themselves, as two ordinary operations. Do not offer an action that does both, an older-version install from a store, or an "Undo" after an install (the official store and the AI window drop theirs). Uninstalling without `removeData` keeps the plugin's data, and a reinstall from the same source reconnects to it; a `.vxp` file is a different source from a repository, so a plugin first installed from the store gets a new, empty data directory.
|
|
1875
|
+
|
|
1876
|
+
#### 3. Remove "Undo install"
|
|
1877
|
+
|
|
1878
|
+
Remove any Undo action after an install, its texts and translations, and the code that only served it: copies of installed `.vxp` files, remembered "last install" state, and tests of it. A user who wants a plugin gone uninstalls it.
|
|
1879
|
+
|
|
1880
|
+
#### 4. Built-in plugins
|
|
1881
|
+
|
|
1882
|
+
Check `builtin` before offering actions:
|
|
1883
|
+
|
|
1884
|
+
```ts
|
|
1885
|
+
for (const info of await plugin.packages.list()) {
|
|
1886
|
+
const actions = info.builtin
|
|
1887
|
+
? [info.state === "disabled" ? "Enable" : "Disable"]
|
|
1888
|
+
: ["Disable", "Uninstall"];
|
|
1889
|
+
console.log(info.id, actions);
|
|
1890
|
+
}
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
- Offer only disable and enable for a built-in (`packages.setEnabled`); never uninstall or install over it.
|
|
1894
|
+
- Never write into a built-in's `directory`, or into any plugin's `directory`: read `manifest.json` from it if you need to. A plugin writes only under its own plugin.dataDirectory.
|
|
1895
|
+
|
|
1896
|
+
#### 5. Tests
|
|
1897
|
+
|
|
1898
|
+
`TestHost` lists every plugin with `previousVersion: null`, whatever a test puts in `host.packages`, and rejects `packages.rollback` with `unsupported`. The `previousVersion` field of `host.packages` entries is deprecated and ignored; it stays required in 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling, and goes in 1.5.17. A test that expected a rollback to succeed should now assert the rejection or be removed.
|
|
1899
|
+
|
|
1900
|
+
#### Timeline
|
|
1901
|
+
|
|
1902
|
+
| SDK | Player | What happens |
|
|
1903
|
+
| --- | --- | --- |
|
|
1904
|
+
| 1.5.16 | ships with it | `Packages.rollback` and `PluginInfo.previousVersion` (and the wire field) are `@deprecated`. The player and `TestHost` answer rollback with `unsupported` and list `previousVersion: null`. Code still compiles. |
|
|
1905
|
+
| 1.5.17 | — | Both symbols are removed. Remaining uses are TypeScript compile errors. |
|
|
1906
|
+
|
|
1907
|
+
`apiVersion` stays `vestra/1` in both releases, as when 1.5.8 removed `PackageCandidate.trial()`.
|
|
1908
|
+
|
|
1909
|
+
#### Before you hand it in
|
|
1910
|
+
|
|
1911
|
+
- [ ] No call to `packages.rollback`; no UI or logic gated on `previousVersion`.
|
|
1912
|
+
- [ ] Built-ins offer only disable and enable.
|
|
1913
|
+
- [ ] Nothing writes into a plugin's `directory`.
|
|
1914
|
+
- [ ] No Undo after an install, no older-version install; older versions go through uninstall and a user-chosen `.vxp`.
|
|
1915
|
+
- [ ] Tests pass with SDK 1.5.16; the only deprecated name left in them is the `previousVersion: null` of `host.packages` test data.
|
|
1916
|
+
|
|
1842
1917
|
## Guideline: Interface guidelines
|
|
1843
1918
|
|
|
1844
1919
|
These are the rules the player's built-in plugins follow for their windows, panels and messages. Follow them and your plugin looks like part of the player. They assume you know the five surfaces from Windows, panels, settings pages, dialogs and the OSD; token names, sizes, fonts, icons and widgets are listed in Theme.
|
|
@@ -2080,6 +2155,7 @@ The table lists the API whose documentation names a player version, newest last:
|
|
|
2080
2155
|
| 0.4.1 | PluginError, FileResolverOptions, PackageCandidate.install, PluginWarning.code, Packages.validate |
|
|
2081
2156
|
| 0.4.2 | Resolvers.fileTypes, Resolvers.onFileTypesChange |
|
|
2082
2157
|
| 0.4.3 | CommandOptions.path, PropValue, View.set, Appearance.setLanguage, TemplateArgs, Text, ConfirmOptions.args, OsdPanel.set, OsdPanelOptions.maxColumns, Osd.show |
|
|
2158
|
+
| 0.5.1 | SlotState.reason, Panel.setInteraction, PlaybackStats.presentedFps |
|
|
2083
2159
|
|
|
2084
2160
|
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).
|
|
2085
2161
|
|
|
@@ -2124,6 +2200,8 @@ A plugin lists what it may do in the manifest's `capabilities` (Manifest). A cal
|
|
|
2124
2200
|
- A file resolver (Resolvers.add with file types) also needs `producer.av`.
|
|
2125
2201
|
- Without `events`, Player.state is read at start and after the plugin's own operations, and `onChange` listeners do not fire for changes made elsewhere.
|
|
2126
2202
|
|
|
2203
|
+
**Deprecated:** Packages.rollback in the `plugins.manage` row (since 1.5.16; removed in 1.5.17). The player keeps no previous version, so it always fails with `unsupported`; see Migrating off package rollback.
|
|
2204
|
+
|
|
2127
2205
|
#### Without a capability
|
|
2128
2206
|
|
|
2129
2207
|
A member not in the table needs no capability: `plugin.tools`, `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui` (images and Markdown), `plugin.i18n`, reading window.state, appearance.theme and packages.list. The table is `CAPABILITY_INFO`, which tests can import from `@vestra-player/sdk/testing` to check a manifest (CAPABILITY_INFO).
|
|
@@ -3059,6 +3137,19 @@ change).
|
|
|
3059
3137
|
| `host` | Any other failure inside the player; `message` is the player's reason. |
|
|
3060
3138
|
| `disabled` | The plugin is being stopped; pending requests fail with it. |
|
|
3061
3139
|
| `unknown`, `rejected`, `expired` | After a transport problem the SDK could not confirm the outcome (does not happen in normal operation). |
|
|
3140
|
+
| `activation` | The plugin opened a tool window while its activation was still pending and the player could not track it. |
|
|
3141
|
+
| `command` | The player could not run or update a command it no longer knows for this plugin (removed meanwhile, or another plugin's). |
|
|
3142
|
+
| `input-overflow` | `openStream` input: the player's queue of physical key transitions was full; the input was revoked and its keys released. |
|
|
3143
|
+
| `internal` | An internal step of the player failed (keeping an operation's result, a subtitle or media job); `message` has the reason. |
|
|
3144
|
+
| `javascript`, `slint` | Packages.validate: the package's entry module does not pass `node --check` / one of its Slint files does not compile; `message` has the diagnostics. |
|
|
3145
|
+
| `owner` | A resolver answered a request that belongs to another plugin's resolver. |
|
|
3146
|
+
| `pending` | An operation with the same token is still running in the player. The SDK waits and asks again, so it rarely reaches a plugin. |
|
|
3147
|
+
| `producer` | `openStream`: the player refused or lost the generated stream; `message` has the reason. |
|
|
3148
|
+
| `resolution` | A file or web resolver's result could not be opened; `message` has the reason. |
|
|
3149
|
+
| `revisionConflict` | Packages: the installed revision changed since it was read (another install or uninstall ran). Read again and retry; the SDK passes the revision it read, so this reaches plugins that act on old data. |
|
|
3150
|
+
| `source-geometry`, `source-geometry-unsupported` | The source's picture size or rotation could not be applied: invalid decoder dimensions / a rotation that is not a multiple of 90°. |
|
|
3151
|
+
| `source-unavailable` | The source is not ready (still loading, or its time or native binding is not available yet). |
|
|
3152
|
+
| `unknown-expired` | The player no longer knows the operation or snapshot (it expired or was released). The SDK maps it to `unknownExpired` results where it can; do not resubmit the operation blindly. |
|
|
3062
3153
|
|
|
3063
3154
|
Errors thrown by handlers the SDK invokes go to Scope.onError. A
|
|
3064
3155
|
rejected promise you forgot to `await` is only logged; it never crashes
|
|
@@ -3230,7 +3321,7 @@ interface Plugin extends Scope {
|
|
|
3230
3321
|
* A writable directory for this plugin's own files (settings, caches,
|
|
3231
3322
|
* remembered folders), created by the player before it starts the plugin.
|
|
3232
3323
|
* Each installation has its own directory (player 0.2.0): it is kept across
|
|
3233
|
-
* restarts, disabling
|
|
3324
|
+
* restarts, disabling and upgrades, and a reinstall of the same
|
|
3234
3325
|
* id from a different source gets a new, empty one. It is deleted only by
|
|
3235
3326
|
* `packages.uninstall(id, { removeData: true })`. No capability.
|
|
3236
3327
|
*
|
|
@@ -3662,8 +3753,10 @@ interface SlotState {
|
|
|
3662
3753
|
* - `fullscreen`: the sidebar's fullscreen overlay is collapsed and pops
|
|
3663
3754
|
* out when the pointer reaches the right edge; treat it as "on" for a
|
|
3664
3755
|
* toggle button;
|
|
3665
|
-
* - `idle`: the bottom bar in fullscreen
|
|
3666
|
-
*
|
|
3756
|
+
* - `idle`: the bottom bar in fullscreen while the pointer is outside the
|
|
3757
|
+
* bar's own band at the bottom of the screen (player 0.5.1: keys and
|
|
3758
|
+
* pointer motion elsewhere do not bring it back; before 0.5.1 it hid after
|
|
3759
|
+
* 3 seconds without activity).
|
|
3667
3760
|
*/
|
|
3668
3761
|
reason: "userHidden" | "space" | "fullscreen" | "idle" | null;
|
|
3669
3762
|
/** The sidebar floats in its own window; always `false` for the bottom bar. */
|
|
@@ -4931,10 +5024,15 @@ interface PlaybackStats {
|
|
|
4931
5024
|
backend: string;
|
|
4932
5025
|
/** The playback session number. */
|
|
4933
5026
|
session: number;
|
|
4934
|
-
/** Frames the
|
|
5027
|
+
/** Frames the video renderer produced. Not all of them reach the screen: a busy window shows only the newest one, so compare presentedFps. */
|
|
4935
5028
|
framesShown: number;
|
|
4936
|
-
/** Frames dropped, `null` when unknown. */
|
|
5029
|
+
/** Frames dropped by the backend (mpv), `null` when unknown. */
|
|
4937
5030
|
framesDropped: number | null;
|
|
5031
|
+
/**
|
|
5032
|
+
* New video frames the player window showed per second over the last second, measured by the player itself: draws of the main window that put a new picture on screen. Lower than `video.fps` when frames are lost after decoding and rendering (the window could not keep up with the source, or the display refreshes slower); `0` while paused. `null` when the player cannot measure it (no window, the first second, players before 0.5.1).
|
|
5033
|
+
* @player 0.5.1
|
|
5034
|
+
*/
|
|
5035
|
+
presentedFps: number | null;
|
|
4938
5036
|
/** Every field is the backend's own value, `null` when it has none. */
|
|
4939
5037
|
video: {
|
|
4940
5038
|
/** Codec name. */
|
|
@@ -4959,8 +5057,10 @@ interface PlaybackStats {
|
|
|
4959
5057
|
displayFps: number | null;
|
|
4960
5058
|
/** Bits per second, updated per keyframe. */
|
|
4961
5059
|
bitrate: number | null;
|
|
4962
|
-
/** Hardware decoder in use, e.g. "videotoolbox"; "no" = software decoding. */
|
|
5060
|
+
/** Hardware decoder in use, e.g. "videotoolbox", "d3d11va-copy"; "no" = software decoding. */
|
|
4963
5061
|
hwdec: string | null;
|
|
5062
|
+
/** How the video is decoded, from `hwdec`: `"hardware"` (the method is in `hwdec`) or `"software"`; `null` before a decoder runs. */
|
|
5063
|
+
decoding: "hardware" | "software" | null;
|
|
4964
5064
|
/** Transfer characteristic as mpv names it, e.g. "bt.1886", "pq", "hlg". */
|
|
4965
5065
|
transfer: string | null;
|
|
4966
5066
|
} | null;
|
|
@@ -5861,7 +5961,7 @@ interface PackageCandidate {
|
|
|
5861
5961
|
|
|
5862
5962
|
### PackageChangeStatus
|
|
5863
5963
|
|
|
5864
|
-
The progress of an install
|
|
5964
|
+
The progress of an install or uninstall.
|
|
5865
5965
|
|
|
5866
5966
|
```ts
|
|
5867
5967
|
type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
|
|
@@ -5869,7 +5969,7 @@ type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed
|
|
|
5869
5969
|
|
|
5870
5970
|
### PackageChange
|
|
5871
5971
|
|
|
5872
|
-
An install
|
|
5972
|
+
An install or uninstall in progress. The SDK never submits a
|
|
5873
5973
|
change twice: after a lost reply it only queries the same request.
|
|
5874
5974
|
|
|
5875
5975
|
```ts
|
|
@@ -5905,9 +6005,14 @@ interface PluginInfo {
|
|
|
5905
6005
|
error: string | null;
|
|
5906
6006
|
/** Declared capabilities. */
|
|
5907
6007
|
capabilities: string[];
|
|
5908
|
-
/**
|
|
6008
|
+
/**
|
|
6009
|
+
* The package directory (read its `manifest.json` for display fields). A built-in's directory is inside the player installation (`<install resources>/plugins/<id>`) and read-only: a plugin writes only under its own Plugin.dataDirectory.
|
|
6010
|
+
*/
|
|
5909
6011
|
directory: string;
|
|
5910
|
-
/**
|
|
6012
|
+
/**
|
|
6013
|
+
* Always `null`: the player no longer keeps a previous version, and an upgrade replaces the package.
|
|
6014
|
+
* @deprecated Removed in 1.5.17. The player no longer keeps a previous version, so this is always `null`. See Migrating off package rollback.
|
|
6015
|
+
*/
|
|
5911
6016
|
previousVersion: string | null;
|
|
5912
6017
|
/** Shipped with the player: it can be disabled, not uninstalled or replaced. `false` from players before 0.2.0. */
|
|
5913
6018
|
builtin: boolean;
|
|
@@ -5982,7 +6087,10 @@ interface Packages {
|
|
|
5982
6087
|
* Validate a `.vxp` package file (`plugins.manage`, player 0.2.0): the same checks as Packages.validate after unpacking. Check the file's sha256 yourself before calling when you know it. A reserved id fails with `reserved`, as in Packages.validate.
|
|
5983
6088
|
*/
|
|
5984
6089
|
validateArchive(file: string): Promise<PackageCandidate>;
|
|
5985
|
-
/**
|
|
6090
|
+
/**
|
|
6091
|
+
* Formerly restored a plugin's previous version (`plugins.manage`).
|
|
6092
|
+
* @deprecated Removed in 1.5.17. The player no longer keeps a previous version, so this always fails with `unsupported`. To go back to an older version of a third-party plugin, the user uninstalls it and installs that version's `.vxp`. See Migrating off package rollback.
|
|
6093
|
+
*/
|
|
5986
6094
|
rollback(id: string): Promise<PackageChange>;
|
|
5987
6095
|
/** Every plugin: running, installed, and this run's development loads. No capability. */
|
|
5988
6096
|
list(): Promise<PluginInfo[]>;
|
|
@@ -6456,7 +6564,9 @@ type SubtitleStyle = {
|
|
|
6456
6564
|
outline: number;
|
|
6457
6565
|
/** Bottom margin 2–15 % of the video height. */
|
|
6458
6566
|
marginPercent: number;
|
|
6459
|
-
/**
|
|
6567
|
+
/**
|
|
6568
|
+
* Timing offset in milliseconds, −600000…600000 (±10 minutes); positive shows the subtitles later. Outside the range the player refuses the style with `invalid`.
|
|
6569
|
+
*/
|
|
6460
6570
|
offsetMs: number;
|
|
6461
6571
|
/** Bold text; default false. */
|
|
6462
6572
|
bold?: boolean;
|
|
@@ -7301,7 +7411,7 @@ class TestHost implements Transport {
|
|
|
7301
7411
|
overlay: Map<string, string[]>;
|
|
7302
7412
|
};
|
|
7303
7413
|
/**
|
|
7304
|
-
* What `packages.list` reports; `setEnabled`, `uninstall` and `install` update it. `builtin`, `source` and `dataDirectory` are optional here (default `false`, `null`, `null`).
|
|
7414
|
+
* What `packages.list` reports; `setEnabled`, `uninstall` and `install` update it. `builtin`, `source` and `dataDirectory` are optional here (default `false`, `null`, `null`). Like the player, the host lists every plugin with `previousVersion: null` and answers `packages.rollback` with `unsupported` (since 1.5.16).
|
|
7305
7415
|
*/
|
|
7306
7416
|
packages: {
|
|
7307
7417
|
/** The plugin's manifest id. */
|
|
@@ -7318,7 +7428,10 @@ class TestHost implements Transport {
|
|
|
7318
7428
|
capabilities: string[];
|
|
7319
7429
|
/** The package directory. */
|
|
7320
7430
|
directory: string;
|
|
7321
|
-
/**
|
|
7431
|
+
/**
|
|
7432
|
+
* Ignored: `packages.list` always reports `null`. Still required in 1.5.16 so that test data shaped like a `PluginInfo` keeps compiling.
|
|
7433
|
+
* @deprecated Removed in 1.5.17 with `PluginInfo.previousVersion`.
|
|
7434
|
+
*/
|
|
7322
7435
|
previousVersion: string | null;
|
|
7323
7436
|
/** Shipped with the player: `uninstall` and `install` are `unsupported`. */
|
|
7324
7437
|
builtin?: boolean;
|
|
@@ -8011,10 +8124,15 @@ interface PlaybackStats {
|
|
|
8011
8124
|
backend: string;
|
|
8012
8125
|
/** The playback session number. */
|
|
8013
8126
|
session: number;
|
|
8014
|
-
/** Frames the
|
|
8127
|
+
/** Frames the video renderer produced. Not all of them reach the screen: a busy window shows only the newest one, so compare presentedFps. */
|
|
8015
8128
|
framesShown: number;
|
|
8016
|
-
/** Frames dropped, `null` when unknown. */
|
|
8129
|
+
/** Frames dropped by the backend (mpv), `null` when unknown. */
|
|
8017
8130
|
framesDropped: number | null;
|
|
8131
|
+
/**
|
|
8132
|
+
* New video frames the player window showed per second over the last second, measured by the player itself: draws of the main window that put a new picture on screen. Lower than `video.fps` when frames are lost after decoding and rendering (the window could not keep up with the source, or the display refreshes slower); `0` while paused. `null` when the player cannot measure it (no window, the first second, players before 0.5.1).
|
|
8133
|
+
* @player 0.5.1
|
|
8134
|
+
*/
|
|
8135
|
+
presentedFps: number | null;
|
|
8018
8136
|
/** Every field is the backend's own value, `null` when it has none. */
|
|
8019
8137
|
video: {
|
|
8020
8138
|
/** Codec name. */
|
|
@@ -8039,8 +8157,10 @@ interface PlaybackStats {
|
|
|
8039
8157
|
displayFps: number | null;
|
|
8040
8158
|
/** Bits per second, updated per keyframe. */
|
|
8041
8159
|
bitrate: number | null;
|
|
8042
|
-
/** Hardware decoder in use, e.g. "videotoolbox"; "no" = software decoding. */
|
|
8160
|
+
/** Hardware decoder in use, e.g. "videotoolbox", "d3d11va-copy"; "no" = software decoding. */
|
|
8043
8161
|
hwdec: string | null;
|
|
8162
|
+
/** How the video is decoded, from `hwdec`: `"hardware"` (the method is in `hwdec`) or `"software"`; `null` before a decoder runs. */
|
|
8163
|
+
decoding: "hardware" | "software" | null;
|
|
8044
8164
|
/** Transfer characteristic as mpv names it, e.g. "bt.1886", "pq", "hlg". */
|
|
8045
8165
|
transfer: string | null;
|
|
8046
8166
|
} | null;
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.16: 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> are line numbers in llms-full.txt, next to this file: 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:
|
|
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: L2100–2163.
|
|
37
37
|
|
|
38
38
|
**main.ts**
|
|
39
39
|
|
|
@@ -76,9 +76,9 @@ export default async function activate(plugin: Plugin) {
|
|
|
76
76
|
- `ui.settings`: Settings.page/show
|
|
77
77
|
- `ui.theme`: Appearance.set/setLanguage
|
|
78
78
|
- `secrets`: Secrets.get/set/delete
|
|
79
|
-
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details:
|
|
79
|
+
- No capability: `tools`, `storage`, `system`, `app`, `ui` (images and Markdown), `i18n`, reading window.state, appearance.theme and packages.list. Details: L2164–2208.
|
|
80
80
|
|
|
81
|
-
**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` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide:
|
|
81
|
+
**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` is a TestHost (an in-memory player): `host.setSource(…)`, `host.command(id).run()`, `host.press(key)`, `await host.settle()`, then assert on `host.osd`, `host.window(id).props`… Guide: L1588–1753; TestHost: L6759.
|
|
82
82
|
|
|
83
83
|
**Command line**
|
|
84
84
|
|
|
@@ -106,40 +106,40 @@ export default async function activate(plugin: Plugin) {
|
|
|
106
106
|
- `limit`: A player-wide limit was reached, a property value is over the limits of PropValue
|
|
107
107
|
- `reserved`: The id is `vestra` or starts with `vestra.`
|
|
108
108
|
- `versionMismatch`: The package needs a newer player or another `apiVersion`
|
|
109
|
-
- Others: PluginError, llms-full.txt
|
|
109
|
+
- Others: PluginError, llms-full.txt L3105.
|
|
110
110
|
|
|
111
111
|
## Reference
|
|
112
112
|
|
|
113
113
|
- [llms-full.txt](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt), also node_modules/@vestra-player/sdk/llms-full.txt; the pages are on the [site](https://vestrasys.github.io/vestra-player-sdk/).
|
|
114
114
|
- Tutorials: Getting started L9–214
|
|
115
|
-
- Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–
|
|
116
|
-
- How-to: Keep a slider responsive while dragging
|
|
117
|
-
- Guidelines: Interface guidelines
|
|
118
|
-
- Reference: Manifest
|
|
119
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html):
|
|
120
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html):
|
|
115
|
+
- Concepts: How a plugin runs L215–364; Commands, menus and key bindings L365–462; Playback state, controls and sources L463–585; Video and audio processing L586–663; Windows, panels, settings pages, dialogs and the OSD L664–950
|
|
116
|
+
- How-to: Keep a slider responsive while dragging L951–1125; Remember a window's state between runs L1126–1255; Translate a plugin L1256–1439; Migrate a plugin to English source text L1440–1587; Test a plugin L1588–1753; Publish a plugin L1754–1842; Migrating off package rollback (1.5.16) L1843–1916
|
|
117
|
+
- Guidelines: Interface guidelines L1917–2001; Menu guidelines L2002–2099
|
|
118
|
+
- Reference: Manifest L2100–2163; Capabilities L2164–2208; Limits L2209–2303; Commands, menus and bindings L2304–2772; Platforms L2773–2801; Theme L2802–3006; Command line L3007–3062
|
|
119
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L3063–6594
|
|
120
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html): L6595–7922
|
|
121
121
|
|
|
122
122
|
## API
|
|
123
123
|
|
|
124
124
|
Where each module of `plugin` (`plugin.<module>`) is declared:
|
|
125
125
|
|
|
126
|
-
- Plugin
|
|
127
|
-
- Commands
|
|
126
|
+
- Plugin L3297, Scope L3166, PluginError L3105, Disposable L3078
|
|
127
|
+
- Commands L3509; Windows L3714; Panels L4357; Dialogs L4643; Player L5091; video: VideoFilter L5356, VideoView L5419, RenderOptions L5461; audio: AudioFilter L5380; Subtitles L5559; media: MediaCapture L5719; Resolvers L5824; Packages L6069; Osd L5292; Host L5240; Clipboard L6189; Power L6209; Tools L6242; Storage L6285; System L6308; Secrets L6326; window: MainWindow L4480; App L4561; Bindings L6159; Ui L3601; Timeline L4437; Drops L4013; Appearance L4042; I18n L4184; Settings L4290.
|
|
128
128
|
|
|
129
129
|
## Examples
|
|
130
130
|
|
|
131
131
|
Tested plugins in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples):
|
|
132
132
|
|
|
133
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
134
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
135
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
136
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
137
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
138
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
139
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
140
|
-
- markdown: A window that shows a Markdown document.
|
|
141
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
133
|
+
- hello: The README Quick start: one menu command and an on-screen message. L8346–8435
|
|
134
|
+
- blur: A tool window with one slider that blurs the playing video. L8436–8697
|
|
135
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L8698–9030
|
|
136
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L9031–9215
|
|
137
|
+
- panel-menu: A sidebar panel with its own right-click menu. L9216–9421
|
|
138
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L9422–9581
|
|
139
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L9582–9745
|
|
140
|
+
- markdown: A window that shows a Markdown document. L9746–9940
|
|
141
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L9941–10108
|
|
142
142
|
|
|
143
143
|
## Optional
|
|
144
144
|
|
|
145
|
-
- [Changelog](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md); full doc comments in dist/plugin
|
|
145
|
+
- [Changelog](https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md); full doc comments in node_modules/@vestra-player/sdk/dist/plugin/types.d.ts (the API) and dist/plugin/testing.d.ts (TestHost); Slint library in slint/vestra/.
|