@vestra-player/sdk 1.5.7 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.8
4
+
5
+ **Trial loading is removed; `resolvers.fileTypes`; the CLI under `--permission`.** `apiVersion` stays `vestra/1`. The removal breaks code that called `PackageCandidate.trial()`: only Vestra Player AI did, and it moves to installing (an install can be rolled back).
6
+
7
+ - **Removed: `PackageCandidate.trial()`, the `PackageTrial` type, `PluginInfo.origin` `"trial"` and the error code `cleanup`** (only `PackageTrial.stop` used it). The player (0.4.2) no longer answers `packages.trial`, `packages.trialState`, `packages.trialStateByRequest` or `packages.stopTrial`, and never lists a plugin with origin `"trial"`. Trial loads only supported plugins using `commands`, `ui.window` and `events`, listed the plugin under another id (`<id>.trial.<n>`), and `trial()` returned before activation ended, so the feature was removed instead of extended. Check a package with `validate` and install it; `rollback` restores the previous version.
8
+ - **`plugin.resolvers.fileTypes`** (player 0.4.2): the extensions some plugin's file resolver handles, lower case, sorted, each once, without who registered them; `[]` from older players. Reading needs no capability. **`plugin.resolvers.onFileTypesChange(listener)`** follows it when a file resolver comes or goes (needs `events`). For a playlist that accepts ROMs while an emulator plugin runs. `TestHost`: `host.fileTypes()` (this plugin's file resolvers plus `otherResolvers`) and `host.updateFileTypes()` after changing `otherResolvers`.
9
+ - **`vestra-plugin` runs under Node's permission model** (`--permission`, with `--allow-child-process` for the type checker): looking for `node_modules/@types`, `tsconfig.json` and `.git` in parent folders it may not read threw `ERR_ACCESS_DENIED`; such a folder now counts as absent.
10
+ - `Packages.validate`: the player (0.4.2) keeps the 32 most recent candidates and drops the oldest, whose `install` then fails with `stale`; before, the 33rd validation of a run failed with `busy` until the player restarted.
11
+
3
12
  ## 1.5.7
4
13
 
5
14
  **Replaced or closed streams stop with `null`.** A fix; no API change, `apiVersion` stays `vestra/1`, any player.
@@ -34,7 +34,7 @@
34
34
  */
35
35
  import { spawnSync } from "node:child_process";
36
36
  import { createHash } from "node:crypto";
37
- import { existsSync } from "node:fs";
37
+ import { existsSync as exists } from "node:fs";
38
38
  import {
39
39
  cp,
40
40
  mkdir,
@@ -110,6 +110,20 @@ async function json(path, fallback) {
110
110
  }
111
111
 
112
112
  /** A module from the plugin's project first, else the SDK's own dependencies. */
113
+ /**
114
+ * Whether a path exists. Under Node's permission model (`--permission`)
115
+ * `existsSync` throws for a path outside the allowed ones; such a path
116
+ * counts as absent, as for a user who cannot read it.
117
+ */
118
+ function present(path) {
119
+ try {
120
+ return exists(path);
121
+ } catch (error) {
122
+ if (error?.code === "ERR_ACCESS_DENIED") return false;
123
+ throw error;
124
+ }
125
+ }
126
+
113
127
  function resolveFrom(directory, specifier) {
114
128
  for (const base of [directory, sdkRoot]) {
115
129
  try {
@@ -139,7 +153,7 @@ async function typescript() {
139
153
  async function plugin(path) {
140
154
  const directory = resolve(path);
141
155
  const name = relative(process.cwd(), directory) || basename(directory);
142
- if (!existsSync(join(directory, "manifest.json")))
156
+ if (!present(join(directory, "manifest.json")))
143
157
  fail(`${name}: no manifest.json in ${directory}`);
144
158
  const manifest = await json(join(directory, "manifest.json"));
145
159
  const { problems, hints } = manifestProblems(manifest, directory);
@@ -165,7 +179,7 @@ function displayFiles(target) {
165
179
  const field = name === "README.md" ? "readme" : "changelog";
166
180
  if (
167
181
  target.manifest[field] === undefined &&
168
- existsSync(join(target.directory, name))
182
+ present(join(target.directory, name))
169
183
  )
170
184
  files.add(name);
171
185
  }
@@ -200,7 +214,7 @@ function dependencies(target) {
200
214
  };
201
215
  if (
202
216
  !Object.keys(deps).length ||
203
- existsSync(join(target.directory, "node_modules"))
217
+ present(join(target.directory, "node_modules"))
204
218
  )
205
219
  return;
206
220
  console.log(`${target.name}: installing dependencies (npm ci)`);
@@ -221,7 +235,7 @@ function* upwards(directory) {
221
235
  for (let current = directory; ;) {
222
236
  yield current;
223
237
  const parent = dirname(current);
224
- if (parent === current || existsSync(join(current, ".git"))) return;
238
+ if (parent === current || present(join(current, ".git"))) return;
225
239
  current = parent;
226
240
  }
227
241
  }
@@ -235,13 +249,13 @@ function checkConfig(target, sources) {
235
249
  const roots = [];
236
250
  for (const directory of upwards(target.directory)) {
237
251
  const types = join(directory, "node_modules/@types");
238
- if (existsSync(types)) roots.push(types);
252
+ if (present(types)) roots.push(types);
239
253
  }
240
254
  const node = resolveFrom(target.directory, "@types/node/package.json");
241
255
  if (node) roots.push(dirname(dirname(node)));
242
256
  const base = [...upwards(target.directory)]
243
257
  .map((directory) => join(directory, "tsconfig.json"))
244
- .find((file) => existsSync(file));
258
+ .find((file) => present(file));
245
259
  return {
246
260
  ...(base ? { extends: base } : {}),
247
261
  compilerOptions: {
@@ -262,7 +276,7 @@ async function check(target) {
262
276
  const own = (await files(target.directory)).filter(
263
277
  (file) => isTypeScript(file) || file.endsWith(".mts"),
264
278
  );
265
- const tests = existsSync(join(target.directory, "tests"))
279
+ const tests = present(join(target.directory, "tests"))
266
280
  ? (await readdir(join(target.directory, "tests")))
267
281
  .filter(isTypeScript)
268
282
  .map((file) => join("tests", file))
@@ -308,7 +322,7 @@ function entries(target) {
308
322
  const entry = target.manifest.entry.replace(/^dist\//, "");
309
323
  const candidates = [entry.replace(/\.js$/, ".ts"), entry];
310
324
  const found = candidates.find((file) =>
311
- existsSync(join(target.directory, file)),
325
+ present(join(target.directory, file)),
312
326
  );
313
327
  if (!found)
314
328
  fail(`${target.name}: entry ${target.manifest.entry} has no source file`);
@@ -454,7 +468,7 @@ async function build(target, flag) {
454
468
 
455
469
  async function test(target) {
456
470
  const directory = join(target.directory, "tests");
457
- const tests = existsSync(directory)
471
+ const tests = present(directory)
458
472
  ? (await readdir(directory))
459
473
  .filter((file) => /\.test\.(ts|mjs)$/.test(file))
460
474
  .sort()
@@ -20,8 +20,7 @@ import type { Transport } from "./wire.ts";
20
20
  */
21
21
  export declare const COMMAND_LIMITS: {
22
22
  /**
23
- * Commands one plugin instance may have at the same time (160). A trial
24
- * package is its own instance.
23
+ * Commands one plugin instance may have at the same time (160).
25
24
  */
26
25
  readonly perPlugin: 160;
27
26
  /** Commands of every plugin plus the player's own, together (1024). */
package/dist/commands.js CHANGED
@@ -18,8 +18,7 @@
18
18
  */
19
19
  export const COMMAND_LIMITS = {
20
20
  /**
21
- * Commands one plugin instance may have at the same time (160). A trial
22
- * package is its own instance.
21
+ * Commands one plugin instance may have at the same time (160).
23
22
  */
24
23
  perPlugin: 160,
25
24
  /** Commands of every plugin plus the player's own, together (1024). */
@@ -1,4 +1,4 @@
1
- /** Package validation, trial loading and install/rollback (`plugins.manage`). */
1
+ /** Package validation and install/rollback (`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";
@@ -9,51 +9,6 @@ const TERMINAL = [
9
9
  "failedDegraded",
10
10
  "tooLate",
11
11
  ];
12
- const STARTING = ["preparing", "activating", "starting"];
13
- class TrialHandle {
14
- #scope;
15
- #record;
16
- #stopped;
17
- constructor(scope, record) {
18
- this.#scope = scope;
19
- this.#record = record;
20
- scope.add(this);
21
- }
22
- get status() {
23
- return this.#record.status;
24
- }
25
- /**
26
- * Concurrent calls share one stop; a confirmed stop is final. A failed one
27
- * (code `cleanup`: the host did not confirm cleanup) is forgotten, so the
28
- * next call sends a new stop to the host.
29
- */
30
- stop() {
31
- const host = this.#scope.runtime.host.packages;
32
- if (this.#stopped)
33
- return this.#stopped;
34
- const stopping = (async () => {
35
- this.#record = await host.stopTrial(this.#record.trialId);
36
- while (!this.#record.cleanupConfirmed &&
37
- this.#record.status !== "stopped" &&
38
- this.#record.status !== "failed") {
39
- await sleep(250);
40
- this.#record = await host.trialState(this.#record.trialId);
41
- }
42
- if (!this.#record.cleanupConfirmed)
43
- throw failure("cleanup", `trial cleanup not confirmed: ${this.#record.status}`);
44
- })();
45
- this.#stopped = stopping;
46
- stopping.catch(() => {
47
- if (this.#stopped === stopping)
48
- this.#stopped = undefined;
49
- });
50
- return stopping;
51
- }
52
- async dispose() {
53
- this.#scope.forget(this);
54
- await this.stop().catch(() => { });
55
- }
56
- }
57
12
  class ChangeHandle {
58
13
  #scope;
59
14
  #id;
@@ -123,24 +78,6 @@ async function change(scope, submit, id) {
123
78
  }
124
79
  return new ChangeHandle(scope, operation);
125
80
  }
126
- async function trial(scope, runtime, token) {
127
- const host = runtime.host.packages;
128
- const requestId = randomUUID();
129
- let record;
130
- try {
131
- record = await host.trial(token, requestId);
132
- }
133
- catch (error) {
134
- if (!uncertain(error))
135
- throw error;
136
- record = await host.trialStateByRequest(requestId);
137
- }
138
- while (STARTING.includes(record.status)) {
139
- await sleep(250);
140
- record = await host.trialState(record.trialId);
141
- }
142
- return new TrialHandle(scope, record);
143
- }
144
81
  function installOptions(options) {
145
82
  if (options === undefined)
146
83
  return {};
@@ -171,7 +108,6 @@ function candidate(scope, directory, checked) {
171
108
  digest: checked.digest,
172
109
  manifest: checked.manifest,
173
110
  checks: checked.checks,
174
- trial: () => trial(scope, runtime, checked.token),
175
111
  install: async (options) => {
176
112
  const wire = installOptions(options);
177
113
  return change(scope, (requestId, revision) => runtime.host.packages.install(checked.token, revision, requestId, wire), checked.manifest.id);
@@ -5,8 +5,72 @@
5
5
  import { failure, quiet } from "./runtime.js";
6
6
  import { playerOf } from "./player.js";
7
7
  import { resolvedStream } from "./stream.js";
8
+ import { windowOf } from "./window.js";
8
9
  const isFiles = (options) => "extensions" in options;
10
+ const sameTypes = (a, b) => a.length === b.length && a.every((type, i) => type === b[i]);
11
+ /**
12
+ * One per plugin: the list comes with the `window.state` read before
13
+ * activation (`[]` from older players) and is read again when the plugin
14
+ * first uses it; with `events`, `resolvers.fileTypes` keeps it current.
15
+ */
16
+ class FileTypesService {
17
+ #runtime;
18
+ types;
19
+ #listeners = new Set();
20
+ #started = false;
21
+ constructor(runtime) {
22
+ this.#runtime = runtime;
23
+ this.types = Object.freeze([...(windowOf(runtime).fileTypes ?? [])]);
24
+ }
25
+ start() {
26
+ if (this.#started)
27
+ return;
28
+ this.#started = true;
29
+ this.#runtime.on("resolvers.fileTypes", (data) => {
30
+ const types = data?.fileTypes;
31
+ if (Array.isArray(types) && types.every((t) => typeof t === "string"))
32
+ this.accept(types);
33
+ });
34
+ // What changed between the start-up read and the subscription.
35
+ const window = windowOf(this.#runtime);
36
+ void window.read().then(() => {
37
+ if (window.fileTypes)
38
+ this.accept(window.fileTypes, this.#runtime.can("events"));
39
+ });
40
+ }
41
+ /** `notify`: false without `events`, whose listeners never fire. */
42
+ accept(types, notify = true) {
43
+ if (sameTypes(types, this.types))
44
+ return;
45
+ this.types = Object.freeze([...types]);
46
+ if (notify)
47
+ for (const listener of [...this.#listeners])
48
+ listener([...types]);
49
+ this.#runtime.invalidate();
50
+ }
51
+ watch(listener) {
52
+ this.#listeners.add(listener);
53
+ return { dispose: async () => void this.#listeners.delete(listener) };
54
+ }
55
+ }
56
+ const fileTypesOf = (runtime) => runtime.service("fileTypes", () => new FileTypesService(runtime));
9
57
  export const resolvers = (scope) => ({
58
+ get fileTypes() {
59
+ const service = fileTypesOf(scope.runtime);
60
+ service.start();
61
+ return service.types;
62
+ },
63
+ onFileTypesChange(listener) {
64
+ const service = fileTypesOf(scope.runtime);
65
+ service.start();
66
+ const watch = service.watch((types) => scope.run(listener, types));
67
+ return scope.add({
68
+ dispose: async () => {
69
+ scope.forget(watch);
70
+ await watch.dispose();
71
+ },
72
+ });
73
+ },
10
74
  add(options) {
11
75
  const runtime = scope.runtime;
12
76
  const registration = isFiles(options)
@@ -773,6 +773,27 @@ export declare class TestHost implements Transport {
773
773
  /** Exact host names it resolves (a web resolver), with `schemes`. */
774
774
  hosts?: string[];
775
775
  }[];
776
+ /**
777
+ * What `plugin.resolvers.fileTypes` reports: the extensions of this
778
+ * plugin's file resolvers and of {@link TestHost.otherResolvers}, sorted,
779
+ * each once.
780
+ */
781
+ fileTypes(): string[];
782
+ /**
783
+ * Push `resolvers.fileTypes` when {@link TestHost.fileTypes} changed:
784
+ * called after this plugin's resolvers come and go; call it yourself
785
+ * after changing {@link TestHost.otherResolvers} (another plugin started
786
+ * or stopped).
787
+ *
788
+ * @example
789
+ * ```ts
790
+ * host.otherResolvers.push({ owner: "vestra.nes", extensions: ["nes"] });
791
+ * host.updateFileTypes();
792
+ * await host.settle();
793
+ * assert.deepEqual(plugin.resolvers.fileTypes, ["nes"]);
794
+ * ```
795
+ */
796
+ updateFileTypes(): void;
776
797
  /**
777
798
  * File opens waiting for this plugin's file resolver (`extensions`), by
778
799
  * request id: the path and how it was opened. Opening a matching path
@@ -1095,7 +1116,7 @@ export declare class TestHost implements Transport {
1095
1116
  /** Its version, `null` when unknown. */
1096
1117
  version: string | null;
1097
1118
  /** Where it was loaded from. */
1098
- origin: "installed" | "development" | "trial";
1119
+ origin: "installed" | "development";
1099
1120
  /** Its run state. */
1100
1121
  state: "starting" | "running" | "disabled" | "failed" | "stopped";
1101
1122
  /** Why it failed (`state: "failed"`). */
@@ -773,6 +773,43 @@ export class TestHost {
773
773
  * ```
774
774
  */
775
775
  otherResolvers = [];
776
+ /**
777
+ * What `plugin.resolvers.fileTypes` reports: the extensions of this
778
+ * plugin's file resolvers and of {@link TestHost.otherResolvers}, sorted,
779
+ * each once.
780
+ */
781
+ fileTypes() {
782
+ const types = new Set();
783
+ for (const declaration of [
784
+ ...this.resolvers.values(),
785
+ ...this.otherResolvers,
786
+ ])
787
+ for (const type of declaration.extensions ?? [])
788
+ types.add(type);
789
+ return [...types].sort();
790
+ }
791
+ #sentTypes = "[]";
792
+ /**
793
+ * Push `resolvers.fileTypes` when {@link TestHost.fileTypes} changed:
794
+ * called after this plugin's resolvers come and go; call it yourself
795
+ * after changing {@link TestHost.otherResolvers} (another plugin started
796
+ * or stopped).
797
+ *
798
+ * @example
799
+ * ```ts
800
+ * host.otherResolvers.push({ owner: "vestra.nes", extensions: ["nes"] });
801
+ * host.updateFileTypes();
802
+ * await host.settle();
803
+ * assert.deepEqual(plugin.resolvers.fileTypes, ["nes"]);
804
+ * ```
805
+ */
806
+ updateFileTypes() {
807
+ const types = this.fileTypes();
808
+ if (JSON.stringify(types) === this.#sentTypes)
809
+ return;
810
+ this.#sentTypes = JSON.stringify(types);
811
+ this.emit("resolvers.fileTypes", { fileTypes: types });
812
+ }
776
813
  /**
777
814
  * File opens waiting for this plugin's file resolver (`extensions`), by
778
815
  * request id: the path and how it was opened. Opening a matching path
@@ -1361,6 +1398,7 @@ export class TestHost {
1361
1398
  videoArea: { ...this.mainWindow.videoArea },
1362
1399
  pictureInPicture: { ...this.mainWindow.pictureInPicture },
1363
1400
  appearance: this.appearance,
1401
+ fileTypes: this.fileTypes(),
1364
1402
  };
1365
1403
  }
1366
1404
  /**
@@ -2683,8 +2721,6 @@ export class TestHost {
2683
2721
  const entry = this.packages.find((each) => each.id === id);
2684
2722
  if (!entry)
2685
2723
  throw new HostError("missing", `no plugin ${id}`);
2686
- if (entry.origin === "trial")
2687
- throw new HostError("unsupported", "a trial is stopped with its handle");
2688
2724
  this.packageCalls.push({
2689
2725
  method: "setEnabled",
2690
2726
  id,
@@ -2944,10 +2980,14 @@ export class TestHost {
2944
2980
  return { jobId: job.jobId, cancel: "requested" };
2945
2981
  },
2946
2982
  "jobs.operation": () => ({ status: "unknown" }),
2947
- "sourceResolvers.register": (p) => void this.#register(p),
2983
+ "sourceResolvers.register": (p) => {
2984
+ this.#register(p);
2985
+ this.updateFileTypes();
2986
+ },
2948
2987
  "sourceResolvers.unregister": (p) => {
2949
2988
  this.resolvers.delete(String(p.id));
2950
2989
  this.#cancelResolutions();
2990
+ this.updateFileTypes();
2951
2991
  },
2952
2992
  "sourceResolvers.respond": (p) => this.#respond(p),
2953
2993
  "producer.prepare": async () => {
@@ -73,9 +73,8 @@ export interface Disposable {
73
73
  * | `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
74
74
  * | `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
75
75
  * | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
76
- * | `cleanup` | {@link PackageTrial.stop} could not confirm cleanup; call it again. |
77
76
  * | `limit` | A player-wide limit was reached (for example the number of commands). |
78
- * | `reserved` | {@link Packages.validate}, {@link PackageCandidate.trial}, {@link PackageCandidate.install}: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
77
+ * | `reserved` | {@link Packages.validate}, {@link PackageCandidate.install}: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
79
78
  * | `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
80
79
  * | `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
81
80
  * | `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
@@ -195,7 +194,7 @@ export interface Scope extends Disposable {
195
194
  readonly media: MediaCapture;
196
195
  /** Page-URL to stream-URL resolvers (`source.resolve`). */
197
196
  readonly resolvers: Resolvers;
198
- /** The installed plugins; validation, trials, installs (`plugins.manage` for changes). */
197
+ /** The installed plugins; validation, installs (`plugins.manage` for changes). */
199
198
  readonly packages: Packages;
200
199
  /** On-screen messages over the video (`ui.osd`). */
201
200
  readonly osd: Osd;
@@ -320,8 +319,7 @@ export interface Plugin extends Scope {
320
319
  readonly id: string;
321
320
  /**
322
321
  * A writable directory for this plugin's own files (settings, remembered
323
- * folders). Kept across restarts, disabling and upgrades; a trial gets a
324
- * temporary one. Write files atomically: to a temporary name, then rename.
322
+ * folders). Kept across restarts, disabling and upgrades. Write files atomically: to a temporary name, then rename.
325
323
  * Throws `unavailable` when the player provided none.
326
324
  *
327
325
  * Location: `~/Library/Application Support/Vestra/plugin-data/<id>/` on
@@ -4008,6 +4006,47 @@ export interface Resolvers {
4008
4006
  * error's message.
4009
4007
  */
4010
4008
  add(options: ResolverOptions): Resolver;
4009
+ /**
4010
+ * The extensions some plugin's file resolver handles (SDK 1.5.8, player
4011
+ * 0.4.2): lower case, without the dot, sorted, each once; not who
4012
+ * registered them. Reading needs no capability; `[]` from older players.
4013
+ * Without `events` it is the list from when the plugin first read it.
4014
+ * Use it to accept such files beside the player's own media types, for
4015
+ * example in a playlist's drop or folder scan.
4016
+ *
4017
+ * @example
4018
+ * ```ts
4019
+ * const MEDIA = ["mp4", "mkv", "mp3"];
4020
+ * const accepted = (path: string) => {
4021
+ * const extension = path.split(".").pop()?.toLowerCase() ?? "";
4022
+ * return MEDIA.includes(extension) || plugin.resolvers.fileTypes.includes(extension);
4023
+ * };
4024
+ * ```
4025
+ *
4026
+ * @capability None.
4027
+ * @player 0.4.2
4028
+ * @since 1.5.8
4029
+ */
4030
+ readonly fileTypes: readonly string[];
4031
+ /**
4032
+ * {@link Resolvers.fileTypes} changed: a file resolver was added, or its
4033
+ * plugin stopped. Needs `events`; without it the listener never fires.
4034
+ *
4035
+ * @param listener - Receives the new list.
4036
+ * @returns A handle that removes the listener.
4037
+ *
4038
+ * @example
4039
+ * ```ts
4040
+ * plugin.resolvers.onFileTypesChange((types) => {
4041
+ * void plugin.osd.show(`Also accepted: ${types.join(", ")}`);
4042
+ * });
4043
+ * ```
4044
+ *
4045
+ * @capability `events`
4046
+ * @player 0.4.2
4047
+ * @since 1.5.8
4048
+ */
4049
+ onFileTypesChange(listener: (types: string[]) => void): Disposable;
4011
4050
  }
4012
4051
  /**
4013
4052
  * The manifest of a package, as validated. The display fields and
@@ -4099,11 +4138,6 @@ export interface PackageCandidate {
4099
4138
  readonly manifest: PackageManifest;
4100
4139
  /** The checks the player ran. */
4101
4140
  readonly checks: string[];
4102
- /**
4103
- * Load it temporarily alongside the installed plugins. A reserved id
4104
- * (`vestra.*`) fails with `reserved`.
4105
- */
4106
- trial(): Promise<PackageTrial>;
4107
4141
  /**
4108
4142
  * Install it. The same source upgrades the installed plugin (its data
4109
4143
  * stays); another source fails with `conflict` unless `replace` is set;
@@ -4114,17 +4148,6 @@ export interface PackageCandidate {
4114
4148
  */
4115
4149
  install(options?: InstallOptions): Promise<PackageChange>;
4116
4150
  }
4117
- /** A trial load of a package. Disposing it stops the trial and waits for cleanup. */
4118
- export interface PackageTrial extends Disposable {
4119
- /** The trial's status as the player reports it. */
4120
- readonly status: string;
4121
- /**
4122
- * Stop and wait until the host confirmed cleanup. Rejects with code
4123
- * `cleanup` when the host stopped the trial without confirming cleanup;
4124
- * calling `stop()` again then asks the host again.
4125
- */
4126
- stop(): Promise<void>;
4127
- }
4128
4151
  /** The progress of an install, rollback or uninstall. */
4129
4152
  export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
4130
4153
  /**
@@ -4149,8 +4172,8 @@ export interface PluginInfo {
4149
4172
  id: string;
4150
4173
  /** Version, `null` when unknown. */
4151
4174
  version: string | null;
4152
- /** From the install library, a `--plugin` development load, or a trial. */
4153
- origin: "installed" | "development" | "trial";
4175
+ /** From the install library, or a `--plugin` development load. */
4176
+ origin: "installed" | "development";
4154
4177
  /** Run state. */
4155
4178
  state: "starting" | "running" | "disabled" | "failed" | "stopped";
4156
4179
  /** Why it failed (`state: "failed"`). */
@@ -4166,7 +4189,7 @@ export interface PluginInfo {
4166
4189
  * replaced. `false` from players before 0.2.0.
4167
4190
  */
4168
4191
  builtin: boolean;
4169
- /** Where an installed plugin came from; `null` for development loads and trials. */
4192
+ /** Where an installed plugin came from; `null` for development loads. */
4170
4193
  source: PackageSource | null;
4171
4194
  /**
4172
4195
  * Its data directory (`plugin.dataDirectory` of that plugin); `null` when
@@ -4222,6 +4245,10 @@ export interface Packages {
4222
4245
  * Validate a package directory (`plugins.manage`). A package whose id is
4223
4246
  * reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
4224
4247
  * `reserved` (player 0.4.1).
4248
+ *
4249
+ * The player keeps the 32 most recent candidates of all plugins; an
4250
+ * older one's `install` fails with `stale` (validate again). Before
4251
+ * player 0.4.2 the 33rd validation of a run failed with `busy` instead.
4225
4252
  */
4226
4253
  validate(directory: string): Promise<PackageCandidate>;
4227
4254
  /**
@@ -4240,7 +4267,7 @@ export interface Packages {
4240
4267
  /**
4241
4268
  * Stop or start a plugin (`plugins.manage`). An installed plugin stays
4242
4269
  * disabled across launches; a development load only for this run.
4243
- * `invalid` to disable oneself, `unsupported` for a trial, `missing` unknown.
4270
+ * `invalid` to disable oneself, `missing` unknown.
4244
4271
  * `busy` while it is being installed.
4245
4272
  */
4246
4273
  setEnabled(id: string, enabled: boolean): Promise<void>;
@@ -4248,7 +4275,7 @@ export interface Packages {
4248
4275
  * Remove an installed plugin (`plugins.manage`); `removeData` also deletes
4249
4276
  * its data directory, otherwise reinstalling it from the same source
4250
4277
  * reconnects to its data. Its user bindings stay (as orphans).
4251
- * `unsupported` for built-ins, development loads and trials, `missing`
4278
+ * `unsupported` for built-ins and development loads, `missing`
4252
4279
  * unknown, `invalid` for oneself.
4253
4280
  */
4254
4281
  uninstall(id: string, options?: {
@@ -19,6 +19,8 @@ export declare class WindowService {
19
19
  start(): void;
20
20
  /** The palette, as the `window.state` reply carried it (`plugin.appearance`). */
21
21
  appearance: string | undefined;
22
+ /** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
23
+ fileTypes: string[] | undefined;
22
24
  accept(facts: WindowFacts): void;
23
25
  read(): Promise<void>;
24
26
  watch(listener: (state: WindowState) => void): {
@@ -52,9 +52,13 @@ export class WindowService {
52
52
  }
53
53
  /** The palette, as the `window.state` reply carried it (`plugin.appearance`). */
54
54
  appearance;
55
+ /** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
56
+ fileTypes;
55
57
  accept(facts) {
56
58
  if (facts.appearance)
57
59
  this.appearance = facts.appearance;
60
+ if (Array.isArray(facts.fileTypes))
61
+ this.fileTypes = [...facts.fileTypes];
58
62
  const next = view(facts);
59
63
  if (JSON.stringify(next) === JSON.stringify(this.state))
60
64
  return;
package/dist/wire.d.ts CHANGED
@@ -25,12 +25,6 @@ export interface PackageValidation {
25
25
  checks: string[];
26
26
  trustedExecution: true;
27
27
  }
28
- export interface PackageTrial {
29
- trialId: number;
30
- activationOwner?: number;
31
- status: string;
32
- cleanupConfirmed?: boolean;
33
- }
34
28
  export interface InstalledPackage {
35
29
  revision: number;
36
30
  current: null | {
@@ -102,7 +96,7 @@ export declare class HostError extends Error {
102
96
  export interface PackageInfo {
103
97
  id: string;
104
98
  version: string | null;
105
- origin: "installed" | "development" | "trial";
99
+ origin: "installed" | "development";
106
100
  state: "starting" | "running" | "disabled" | "failed" | "stopped";
107
101
  error: string | null;
108
102
  capabilities: string[];
@@ -175,6 +169,8 @@ export interface WindowFacts {
175
169
  };
176
170
  /** The palette (`plugin.appearance`), with the `window.state` reply; a newer player may name a palette this SDK does not list. */
177
171
  appearance?: string;
172
+ /** The file resolvers' extensions (`plugin.resolvers.fileTypes`; player 0.4.2). */
173
+ fileTypes?: string[];
178
174
  }
179
175
  /** One timeline mark as the player merges them (`timeline.marks`). */
180
176
  export interface WireMark {
@@ -530,11 +526,6 @@ export declare function createWire(transport: Transport): {
530
526
  validate: (directory: string, apiVersion?: string) => Promise<PackageValidation>;
531
527
  /** Player 0.2.0: a `.vxp` file. */
532
528
  validateArchive: (file: string) => Promise<PackageValidation>;
533
- /** `requestId` recovers a lost first reply in this owner session. */
534
- trial: (token: string, requestId: string) => Promise<PackageTrial>;
535
- trialStateByRequest: (requestId: string) => Promise<PackageTrial>;
536
- trialState: (trialId: number) => Promise<PackageTrial>;
537
- stopTrial: (trialId: number) => Promise<PackageTrial>;
538
529
  installed: (id: string) => Promise<InstalledPackage>;
539
530
  /** `source` and `replace` are sent only when given (player 0.2.0). */
540
531
  install: (token: string, expectedRevision: number, requestId: string, options?: {
package/dist/wire.js CHANGED
@@ -258,16 +258,6 @@ export function createWire(transport) {
258
258
  validateArchive: (file) => transport.request("packages.validateArchive", {
259
259
  file,
260
260
  }),
261
- /** `requestId` recovers a lost first reply in this owner session. */
262
- trial: (token, requestId) => transport.request("packages.trial", {
263
- token,
264
- requestId,
265
- }),
266
- trialStateByRequest: (requestId) => transport.request("packages.trialStateByRequest", {
267
- requestId,
268
- }),
269
- trialState: (trialId) => transport.request("packages.trialState", { trialId }),
270
- stopTrial: (trialId) => transport.request("packages.stopTrial", { trialId }),
271
261
  installed: (id) => transport.request("packages.installed", { id }),
272
262
  /** `source` and `replace` are sent only when given (player 0.2.0). */
273
263
  install: (token, expectedRevision, requestId, options = {}) => transport.request("packages.install", {
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.7: full reference
1
+ # @vestra-player/sdk 1.5.8: 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
 
@@ -124,7 +124,7 @@ A manifest with every field. `id`, `apiVersion`, `entry` and `capabilities` are
124
124
  }
125
125
  ```
126
126
 
127
- Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install or trial such a package (`reserved`). Every other plugin is a third-party plugin, including those hosted in the official repository.
127
+ Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install such a package (`reserved`). Every other plugin is a third-party plugin, including those hosted in the official repository.
128
128
 
129
129
  | Capability | Opens |
130
130
  | -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
@@ -998,9 +998,8 @@ change).
998
998
  | `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
999
999
  | `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
1000
1000
  | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
1001
- | `cleanup` | PackageTrial.stop could not confirm cleanup; call it again. |
1002
1001
  | `limit` | A player-wide limit was reached (for example the number of commands). |
1003
- | `reserved` | Packages.validate, PackageCandidate.trial, PackageCandidate.install: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
1002
+ | `reserved` | Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
1004
1003
  | `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
1005
1004
  | `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
1006
1005
  | `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
@@ -1080,7 +1079,7 @@ interface Scope extends Disposable {
1080
1079
  readonly media: MediaCapture;
1081
1080
  /** Page-URL to stream-URL resolvers (`source.resolve`). */
1082
1081
  readonly resolvers: Resolvers;
1083
- /** The installed plugins; validation, trials, installs (`plugins.manage` for changes). */
1082
+ /** The installed plugins; validation, installs (`plugins.manage` for changes). */
1084
1083
  readonly packages: Packages;
1085
1084
  /** On-screen messages over the video (`ui.osd`). */
1086
1085
  readonly osd: Osd;
@@ -1174,8 +1173,7 @@ interface Plugin extends Scope {
1174
1173
  readonly id: string;
1175
1174
  /**
1176
1175
  * A writable directory for this plugin's own files (settings, remembered
1177
- * folders). Kept across restarts, disabling and upgrades; a trial gets a
1178
- * temporary one. Write files atomically: to a temporary name, then rename.
1176
+ * folders). Kept across restarts, disabling and upgrades. Write files atomically: to a temporary name, then rename.
1179
1177
  * Throws `unavailable` when the player provided none.
1180
1178
  *
1181
1179
  * Location: `~/Library/Application Support/Vestra/plugin-data/<id>/` on
@@ -3450,6 +3448,20 @@ interface Resolvers {
3450
3448
  * Register a resolver. When anyone opens a matching URL or file the player calls `resolve`; `signal` aborts when the open is cancelled or replaced. One scheme + host pair, and one extension, has one resolver (`conflict` otherwise). A rejected `resolve` fails the open with the error's message.
3451
3449
  */
3452
3450
  add(options: ResolverOptions): Resolver;
3451
+ /**
3452
+ * The extensions some plugin's file resolver handles (SDK 1.5.8, player 0.4.2): lower case, without the dot, sorted, each once; not who registered them. Reading needs no capability; `[]` from older players. Without `events` it is the list from when the plugin first read it. Use it to accept such files beside the player's own media types, for example in a playlist's drop or folder scan.
3453
+ * @capability None.
3454
+ * @player 0.4.2
3455
+ */
3456
+ readonly fileTypes: readonly string[];
3457
+ /**
3458
+ * Resolvers.fileTypes changed: a file resolver was added, or its plugin stopped. Needs `events`; without it the listener never fires.
3459
+ * @param listener - Receives the new list.
3460
+ * @returns A handle that removes the listener.
3461
+ * @capability `events`
3462
+ * @player 0.4.2
3463
+ */
3464
+ onFileTypesChange(listener: (types: string[]) => void): Disposable;
3453
3465
  }
3454
3466
  ```
3455
3467
 
@@ -3555,8 +3567,6 @@ interface PackageCandidate {
3555
3567
  readonly manifest: PackageManifest;
3556
3568
  /** The checks the player ran. */
3557
3569
  readonly checks: string[];
3558
- /** Load it temporarily alongside the installed plugins. A reserved id (`vestra.*`) fails with `reserved`. */
3559
- trial(): Promise<PackageTrial>;
3560
3570
  /**
3561
3571
  * Install it. The same source upgrades the installed plugin (its data stays); another source fails with `conflict` unless `replace` is set; a built-in id fails with `unsupported` (built-ins update with the player). A reserved id (`vestra` or `vestra.*`, which every built-in uses) fails with `reserved` (player 0.4.1; already when validated), as only the player installs built-in plugins.
3562
3572
  */
@@ -3564,21 +3574,6 @@ interface PackageCandidate {
3564
3574
  }
3565
3575
  ```
3566
3576
 
3567
- ### PackageTrial
3568
-
3569
- A trial load of a package. Disposing it stops the trial and waits for cleanup.
3570
-
3571
- ```ts
3572
- interface PackageTrial extends Disposable {
3573
- /** The trial's status as the player reports it. */
3574
- readonly status: string;
3575
- /**
3576
- * Stop and wait until the host confirmed cleanup. Rejects with code `cleanup` when the host stopped the trial without confirming cleanup; calling `stop()` again then asks the host again.
3577
- */
3578
- stop(): Promise<void>;
3579
- }
3580
- ```
3581
-
3582
3577
  ### PackageChangeStatus
3583
3578
 
3584
3579
  The progress of an install, rollback or uninstall.
@@ -3617,8 +3612,8 @@ interface PluginInfo {
3617
3612
  id: string;
3618
3613
  /** Version, `null` when unknown. */
3619
3614
  version: string | null;
3620
- /** From the install library, a `--plugin` development load, or a trial. */
3621
- origin: "installed" | "development" | "trial";
3615
+ /** From the install library, or a `--plugin` development load. */
3616
+ origin: "installed" | "development";
3622
3617
  /** Run state. */
3623
3618
  state: "starting" | "running" | "disabled" | "failed" | "stopped";
3624
3619
  /** Why it failed (`state: "failed"`). */
@@ -3631,7 +3626,7 @@ interface PluginInfo {
3631
3626
  previousVersion: string | null;
3632
3627
  /** Shipped with the player: it can be disabled, not uninstalled or replaced. `false` from players before 0.2.0. */
3633
3628
  builtin: boolean;
3634
- /** Where an installed plugin came from; `null` for development loads and trials. */
3629
+ /** Where an installed plugin came from; `null` for development loads. */
3635
3630
  source: PackageSource | null;
3636
3631
  /** Its data directory (`plugin.dataDirectory` of that plugin); `null` when the player has none. The path's layout is not part of the API. */
3637
3632
  dataDirectory: string | null;
@@ -3667,7 +3662,13 @@ need no capability; everything else needs `plugins.manage`.
3667
3662
  ```ts
3668
3663
  interface Packages {
3669
3664
  /**
3670
- * Validate a package directory (`plugins.manage`). A package whose id is reserved for the built-in plugins (`vestra` or `vestra.*`) fails with `reserved` (player 0.4.1).
3665
+ * Validate a package directory (`plugins.manage`). A package whose id is
3666
+ * reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
3667
+ * `reserved` (player 0.4.1).
3668
+ *
3669
+ * The player keeps the 32 most recent candidates of all plugins; an
3670
+ * older one's `install` fails with `stale` (validate again). Before
3671
+ * player 0.4.2 the 33rd validation of a run failed with `busy` instead.
3671
3672
  */
3672
3673
  validate(directory: string): Promise<PackageCandidate>;
3673
3674
  /**
@@ -3681,11 +3682,11 @@ interface Packages {
3681
3682
  /** The new list after any start, stop, failure, install or uninstall. No capability. */
3682
3683
  onChange(listener: (plugins: PluginInfo[]) => void): Disposable;
3683
3684
  /**
3684
- * Stop or start a plugin (`plugins.manage`). An installed plugin stays disabled across launches; a development load only for this run. `invalid` to disable oneself, `unsupported` for a trial, `missing` unknown. `busy` while it is being installed.
3685
+ * Stop or start a plugin (`plugins.manage`). An installed plugin stays disabled across launches; a development load only for this run. `invalid` to disable oneself, `missing` unknown. `busy` while it is being installed.
3685
3686
  */
3686
3687
  setEnabled(id: string, enabled: boolean): Promise<void>;
3687
3688
  /**
3688
- * Remove an installed plugin (`plugins.manage`); `removeData` also deletes its data directory, otherwise reinstalling it from the same source reconnects to its data. Its user bindings stay (as orphans). `unsupported` for built-ins, development loads and trials, `missing` unknown, `invalid` for oneself.
3689
+ * Remove an installed plugin (`plugins.manage`); `removeData` also deletes its data directory, otherwise reinstalling it from the same source reconnects to its data. Its user bindings stay (as orphans). `unsupported` for built-ins and development loads, `missing` unknown, `invalid` for oneself.
3689
3690
  */
3690
3691
  uninstall(id: string, options?: {
3691
3692
  removeData?: boolean;
@@ -4721,6 +4722,12 @@ class TestHost implements Transport {
4721
4722
  /** Exact host names it resolves (a web resolver), with `schemes`. */
4722
4723
  hosts?: string[];
4723
4724
  }[];
4725
+ /** What `plugin.resolvers.fileTypes` reports: the extensions of this plugin's file resolvers and of TestHost.otherResolvers, sorted, each once. */
4726
+ fileTypes(): string[];
4727
+ /**
4728
+ * Push `resolvers.fileTypes` when TestHost.fileTypes changed: called after this plugin's resolvers come and go; call it yourself after changing TestHost.otherResolvers (another plugin started or stopped).
4729
+ */
4730
+ updateFileTypes(): void;
4724
4731
  /**
4725
4732
  * File opens waiting for this plugin's file resolver (`extensions`), by request id: the path and how it was opened. Opening a matching path (`player.open`, TestHost.openElsewhere) sends `source.resolveRequest`; the answer selects the stream, fails the open or, once the source changed, is `stale`. Any later source change cancels it (`source.resolveCancel`). Web resolvers are not simulated: emit `source.resolveRequest` yourself.
4726
4733
  */
@@ -4905,7 +4912,7 @@ class TestHost implements Transport {
4905
4912
  /** Its version, `null` when unknown. */
4906
4913
  version: string | null;
4907
4914
  /** Where it was loaded from. */
4908
- origin: "installed" | "development" | "trial";
4915
+ origin: "installed" | "development";
4909
4916
  /** Its run state. */
4910
4917
  state: "starting" | "running" | "disabled" | "failed" | "stopped";
4911
4918
  /** Why it failed (`state: "failed"`). */
@@ -5059,7 +5066,7 @@ own sensible display cap well below `perPlugin`.
5059
5066
 
5060
5067
  ```ts
5061
5068
  const COMMAND_LIMITS: {
5062
- /** Commands one plugin instance may have at the same time (160). A trial package is its own instance. */
5069
+ /** Commands one plugin instance may have at the same time (160). */
5063
5070
  readonly perPlugin: 160;
5064
5071
  /** Commands of every plugin plus the player's own, together (1024). */
5065
5072
  readonly total: 1024;
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.7: 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. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
3
+ > @vestra-player/sdk 1.5.8: 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. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
4
4
 
5
5
  **How plugins run**
6
6
 
@@ -136,7 +136,7 @@ test("shows the position", async () => {
136
136
  - `aborted`: An `AbortSignal` fired
137
137
  - `timeout`: A time limit ran out
138
138
  - `limit`: A player-wide limit was reached
139
- - `reserved`: Packages.validate, PackageCandidate.trial, PackageCandidate.install: the id is `vestra` or starts with `vestra.`, which only the player's built-in plugins use
139
+ - `reserved`: Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.`, which only the player's built-in plugins use
140
140
  - `versionMismatch`: The package needs a newer player or another `apiVersion`
141
141
  - `ui-prepare`: The player could not prepare a mounted panel or a settings page
142
142
  - `ui`: The player could not apply a property to a tool window or a settings page, or could not build the Options window
@@ -150,54 +150,54 @@ test("shows the position", async () => {
150
150
  - [Concept: Windows, panels, settings pages, dialogs and the OSD](https://vestrasys.github.io/vestra-player-sdk/documents/Windows__panels__settings_pages__dialogs_and_the_OSD.html): L281–626
151
151
  - [How-to: Keep a slider responsive while dragging](https://vestrasys.github.io/vestra-player-sdk/documents/Keep_a_slider_responsive_while_dragging.html): L627–801
152
152
  - [How-to: Remember a window's state between runs](https://vestrasys.github.io/vestra-player-sdk/documents/Remember_a_window_s_state_between_runs.html): L802–931
153
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4172
154
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4173–5113
153
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4173
154
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4174–5120
155
155
 
156
156
  ## API
157
157
 
158
158
  Where each module of `plugin` is declared:
159
159
 
160
- - Plugin L1152, Scope L1023, PluginError L974, Disposable L947
161
- - plugin.commands: Commands L1349
162
- - plugin.windows: Windows L1554
163
- - plugin.panels: Panels L2005
164
- - plugin.dialogs: Dialogs L2287
165
- - plugin.player: Player L2726
166
- - plugin.video: VideoFilter L2975, VideoView L3038, RenderOptions L3080
167
- - plugin.audio: AudioFilter L2999
168
- - plugin.subtitles: Subtitles L3178
169
- - plugin.media: MediaCapture L3336
170
- - plugin.resolvers: Resolvers L3441
171
- - plugin.packages: Packages L3662
172
- - plugin.osd: Osd L2912
173
- - plugin.host: Host L2875
174
- - plugin.clipboard: Clipboard L3769
175
- - plugin.power: Power L3789
176
- - plugin.tools: Tools L3822
177
- - plugin.storage: Storage L3865
178
- - plugin.system: System L3888
179
- - plugin.secrets: Secrets L3906
180
- - plugin.window: MainWindow L2128
181
- - plugin.app: App L2209
182
- - plugin.bindings: Bindings L3739
183
- - plugin.ui: Ui L1441
184
- - plugin.timeline: Timeline L2085
185
- - plugin.drops: Drops L1851
186
- - plugin.appearance: Appearance L1880
187
- - plugin.settings: Settings L1938
160
+ - Plugin L1151, Scope L1022, PluginError L974, Disposable L947
161
+ - plugin.commands: Commands L1347
162
+ - plugin.windows: Windows L1552
163
+ - plugin.panels: Panels L2003
164
+ - plugin.dialogs: Dialogs L2285
165
+ - plugin.player: Player L2724
166
+ - plugin.video: VideoFilter L2973, VideoView L3036, RenderOptions L3078
167
+ - plugin.audio: AudioFilter L2997
168
+ - plugin.subtitles: Subtitles L3176
169
+ - plugin.media: MediaCapture L3334
170
+ - plugin.resolvers: Resolvers L3439
171
+ - plugin.packages: Packages L3657
172
+ - plugin.osd: Osd L2910
173
+ - plugin.host: Host L2873
174
+ - plugin.clipboard: Clipboard L3770
175
+ - plugin.power: Power L3790
176
+ - plugin.tools: Tools L3823
177
+ - plugin.storage: Storage L3866
178
+ - plugin.system: System L3889
179
+ - plugin.secrets: Secrets L3907
180
+ - plugin.window: MainWindow L2126
181
+ - plugin.app: App L2207
182
+ - plugin.bindings: Bindings L3740
183
+ - plugin.ui: Ui L1439
184
+ - plugin.timeline: Timeline L2083
185
+ - plugin.drops: Drops L1849
186
+ - plugin.appearance: Appearance L1878
187
+ - plugin.settings: Settings L1936
188
188
 
189
189
  ## Examples
190
190
 
191
191
  Complete plugins with tests, in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples) and in llms-full.txt:
192
192
 
193
- - hello: The README Quick start: one menu command and an on-screen message. L5527–5616
194
- - blur: A tool window with one slider that blurs the playing video. L5617–5878
195
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5879–6211
196
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6212–6396
197
- - panel-menu: A sidebar panel with its own right-click menu. L6397–6602
198
- - menus: Radio menu items, separator groups and a menu item whose title changes. L6603–6762
199
- - theme-widgets: A tool window drawn with the player's theme and widgets. L6763–6926
200
- - markdown: A window that shows a Markdown document. L6927–7120
193
+ - hello: The README Quick start: one menu command and an on-screen message. L5534–5623
194
+ - blur: A tool window with one slider that blurs the playing video. L5624–5885
195
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5886–6218
196
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6219–6403
197
+ - panel-menu: A sidebar panel with its own right-click menu. L6404–6609
198
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L6610–6769
199
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L6770–6933
200
+ - markdown: A window that shows a Markdown document. L6934–7127
201
201
 
202
202
  ## Optional
203
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.7",
3
+ "version": "1.5.8",
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": {