@vestra-player/sdk 1.5.52 → 1.5.54
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 +17 -0
- package/dist/commands.js +1 -0
- package/dist/docs-tree.json.gz +0 -0
- package/dist/plugin/index.d.ts +11 -1
- package/dist/plugin/index.js +3 -2
- package/dist/plugin/runtime.d.ts +10 -1
- package/dist/plugin/runtime.js +22 -0
- package/dist/plugin/scope.d.ts +3 -2
- package/dist/plugin/scope.js +9 -1
- package/dist/plugin/testing.d.ts +74 -6
- package/dist/plugin/testing.js +131 -8
- package/dist/plugin/types.d.ts +119 -4
- package/llms.txt +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.54
|
|
4
|
+
|
|
5
|
+
**Who must change: nobody, but check cleanup functions passed directly to `onDispose`.** Additive: `Plugin.stopReason`, the types `StopReason` and `DisposeInfo`, an optional argument for `onDispose` cleanups, `TestHost.previousCrash`, an optional `code` for `TestHost.failVideoShaders` and `stop({ reason })` / `disconnect()` from `startPlugin`; `apiVersion` stays `vestra/1`, no new capability, and existing code still compiles. One runtime difference: with this SDK in the player (or in `TestHost`) an `onDispose` cleanup is called with one argument, the `DisposeInfo`. A function that takes optional parameters of its own and is passed directly now receives it as its first argument: `plugin.onDispose(controller.abort.bind(controller))` aborts with the `DisposeInfo` as the abort reason, `onDispose(save)` with `save(force?: boolean)` gets an object as `force`. Wrap such a function (`() => controller.abort()`). The stop reasons are planned for player 0.6.4; `previous-crash` comes from player 0.6.3.
|
|
6
|
+
|
|
7
|
+
- **Why the plugin stops** (API request of the Shader Upscaling author). A plugin can tell an orderly stop from a lost player: `plugin.stopReason` is set when the stop begins, and every `onDispose` cleanup receives a `DisposeInfo` (`{ stopping, reason }`); a cleanup without parameters works as before. `stopping` is `false` for a child scope the plugin's own code disposed. Planned for player 0.6.4 (not released yet): the player sends `quit`, `disable`, `uninstall`, `reload`, `upgrade`, `idle` or `failed` with `runtime.stop`, and its runner passes `host-lost` when its input closes (or its output breaks) without a `runtime.stop` (the player crashed or was killed; requests are refused, clean up locally only). The runner hands the reason to the SDK (`PluginInstance.dispose({ reason })` in `@vestra-player/sdk/runtime`; a reason the SDK does not know reads as `undefined`).
|
|
8
|
+
- **Older players.** Players up to 0.6.3 give no reason for any stop, a lost player included. One whose own SDK is older than 1.5.54 (0.6.3 and earlier) calls the cleanup **without an argument** and has no `plugin.stopReason` (it reads `undefined`): the parameter is optional (`info?: DisposeInfo`), so read it as `info?.reason`.
|
|
9
|
+
- **`previous-crash`** (player 0.6.3) is documented on `VideoShaders`, `RenderOptions` and in the `PluginError` table: after a run of the player that ended while plugin shaders were drawing (a crash, the process being ended, a lost GPU device), the player refuses every non-empty shader list, the render options' `shaders` included, with `previous-crash` and a message naming the plugins; one such end keeps them off for the next start, two within a day until the GPU, its driver or the player changes, or for 7 days. Clearing still works, the player shows one OSD line itself, and plugins should not set the list again while it is refused (the SDK does not retry a `failed` handle). The render options send every value set so far with each `set`, the refused `shaders` included: include `shaders: []` in the next `set` to go on without them.
|
|
10
|
+
- **`TestHost`**: `previousCrash` (a message, default `null`) refuses non-empty `video.shaders` lists and render options `shaders` with `previous-crash` as the player does, after the files are checked and before the GPU check. `failVideoShaders(id, message, code?)` (`failed` by default, or `previous-crash`) and `failVideoRender("previous-crash", …)` are shortcuts that only set the handle's status: the real player refuses the request instead, which `previousCrash` plays. `startPlugin`'s `stop` still declares no parameter (`stop.length === 0`, `t.after(stop)`, `promise.then(stop)`) and takes an optional `{ reason }`; `disconnect()` stops the plugin as when the player is lost (`host-lost`; requests in flight and later ones are refused with `disabled`, as the runner does).
|
|
11
|
+
- **`TestHost` cleanup never fails a test.** `close()` (and so `stop()`) retries removing its temporary directory (`maxRetries: 5`, 100 ms apart) and leaves it to the system when Windows still refuses (an intermittent `ENOTEMPTY` on Windows CI, plugin repository run 41351136515).
|
|
12
|
+
- _Docs_: [The plugin model](docs/explanation/plugins.md#stopping), [Host and Node](docs/explanation/host-and-node.md#stopping-a-plugin), [Video and audio processing](docs/explanation/processing.md#shaders), [Test a plugin](docs/how-to/testing.md) and [Reference: testing](docs/reference/testing.md) describe them.
|
|
13
|
+
|
|
14
|
+
## 1.5.53
|
|
15
|
+
|
|
16
|
+
**Who must change: nobody.** No changes to the TypeScript API, `apiVersion` stays `vestra/1`, no new capability, no player version needed.
|
|
17
|
+
|
|
18
|
+
- **`BUILTIN_COMMANDS`** lists the Info plugin's (`vestra.info` 0.1.11, player 0.6.3) top-level **Check for Updates…** (`info.update`, no default key), right after About Vestra Player… (the player's agreement test failed on it). The built-in command table in [Reference: commands](docs/reference/commands.md) follows.
|
|
19
|
+
|
|
3
20
|
## 1.5.52
|
|
4
21
|
|
|
5
22
|
**Who must change: nobody.** Additive: `WindowState` gains `maximized`, `apiVersion` stays `vestra/1`, no new capability. Players before 0.6.3 do not report it and it reads `false` there; a plugin that depends on it sets `"minPlayerVersion": "0.6.3"`. A test that deep-compares the whole `plugin.window.state` now also sees `maximized: false`.
|
package/dist/commands.js
CHANGED
|
@@ -589,6 +589,7 @@ vestra.audio | audio.sync.reset | Reset ▸ Audio Sync | ?
|
|
|
589
589
|
vestra.audio | audio.reset | Reset ▸ Audio Processing
|
|
590
590
|
vestra.visualizer | reset-logo | Reset ▸ Idle Logo
|
|
591
591
|
vestra.info | info.about | About Vestra Player… | F1
|
|
592
|
+
vestra.info | info.update | Check for Updates…
|
|
592
593
|
host | quit | Quit | Cmd+Q
|
|
593
594
|
host | enter-fullscreen | (Enter Fullscreen)
|
|
594
595
|
host | exit-fullscreen | (Leave Fullscreen)
|
package/dist/docs-tree.json.gz
CHANGED
|
Binary file
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -19,7 +19,17 @@ export interface PluginInstance {
|
|
|
19
19
|
* `RuntimeOptions.strictDeclarations` is set.
|
|
20
20
|
*/
|
|
21
21
|
activated(): Promise<void>;
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Stop the plugin: dispose the root scope, then stop the runtime.
|
|
24
|
+
* `info.reason` (SDK 1.5.54) is why: the runner passes `runtime.stop`'s
|
|
25
|
+
* `data.reason` (planned for player 0.6.4), or `"host-lost"` when its input closed
|
|
26
|
+
* without a `runtime.stop`. It reaches `plugin.stopReason` and every
|
|
27
|
+
* `onDispose` cleanup; a reason the SDK does not know reads as
|
|
28
|
+
* `undefined`. A second call runs no cleanup again and keeps the first reason.
|
|
29
|
+
*/
|
|
30
|
+
dispose(info?: {
|
|
31
|
+
reason?: unknown;
|
|
32
|
+
}): Promise<void>;
|
|
23
33
|
}
|
|
24
34
|
export declare function createPlugin(transport: Transport, manifest: PluginManifest, directory: string, dataDirectory?: string,
|
|
25
35
|
/** `plugin.app` (the runner passes the player's `VESTRA_APP_INFO`). */
|
package/dist/plugin/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { failure, Runtime } from "./runtime.js";
|
|
1
|
+
import { failure, Runtime, stopReason } from "./runtime.js";
|
|
2
2
|
import { ScopeImpl } from "./scope.js";
|
|
3
3
|
import { playerOf } from "./player.js";
|
|
4
4
|
import { windowOf } from "./window.js";
|
|
@@ -27,7 +27,8 @@ options) {
|
|
|
27
27
|
return {
|
|
28
28
|
plugin: root,
|
|
29
29
|
activated: () => runtime.activated(),
|
|
30
|
-
dispose: async () => {
|
|
30
|
+
dispose: async (info) => {
|
|
31
|
+
runtime.stopping ??= { reason: stopReason(info?.reason) };
|
|
31
32
|
await root.dispose();
|
|
32
33
|
runtime.stop();
|
|
33
34
|
},
|
package/dist/plugin/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Json, LocalServices, Transport, Wire } from "../wire.ts";
|
|
2
|
-
import type { App, Disposable, Locale, PluginError, SystemInfo } from "./types.ts";
|
|
2
|
+
import type { App, Disposable, Locale, PluginError, StopReason, SystemInfo } from "./types.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Why something listens, for the player's log line that says why a plugin
|
|
5
5
|
* keeps running (`events.subscribe { why }`, player 0.5.21): computed
|
|
@@ -46,6 +46,8 @@ export interface RuntimeOptions {
|
|
|
46
46
|
*/
|
|
47
47
|
strictDeclarations?: boolean;
|
|
48
48
|
}
|
|
49
|
+
/** A stop reason the runner passed on, if this SDK knows it. */
|
|
50
|
+
export declare function stopReason(value: unknown): StopReason | undefined;
|
|
49
51
|
export declare const same: (a: Json | undefined, b: Json | undefined) => boolean;
|
|
50
52
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
51
53
|
export declare class Latest<T> {
|
|
@@ -73,6 +75,13 @@ export declare class Runtime {
|
|
|
73
75
|
readonly views: Map<string, (callback: string, args: Json[]) => void>;
|
|
74
76
|
readonly commands: Map<string, () => void>;
|
|
75
77
|
readonly services: Map<string, unknown>;
|
|
78
|
+
/**
|
|
79
|
+
* Set when the player stops the plugin (`PluginInstance.dispose`), before
|
|
80
|
+
* the root scope is disposed: `DisposeInfo` and `plugin.stopReason`.
|
|
81
|
+
*/
|
|
82
|
+
stopping: {
|
|
83
|
+
reason: StopReason | undefined;
|
|
84
|
+
} | undefined;
|
|
76
85
|
/** `plugin.app`, from the start-up handshake; `system()` asks the player. */
|
|
77
86
|
readonly app: App;
|
|
78
87
|
/** `plugin.i18n.locale`, from the start-up handshake; English when unknown. */
|
package/dist/plugin/runtime.js
CHANGED
|
@@ -96,6 +96,23 @@ const DECLARATION_REFUSALS = new Set([
|
|
|
96
96
|
"conflict",
|
|
97
97
|
"limit",
|
|
98
98
|
]);
|
|
99
|
+
/** The reasons `StopReason` names; anything else reads as `undefined`. */
|
|
100
|
+
const STOP_REASONS = new Set([
|
|
101
|
+
"quit",
|
|
102
|
+
"disable",
|
|
103
|
+
"uninstall",
|
|
104
|
+
"reload",
|
|
105
|
+
"upgrade",
|
|
106
|
+
"idle",
|
|
107
|
+
"failed",
|
|
108
|
+
"host-lost",
|
|
109
|
+
]);
|
|
110
|
+
/** A stop reason the runner passed on, if this SDK knows it. */
|
|
111
|
+
export function stopReason(value) {
|
|
112
|
+
return typeof value === "string" && STOP_REASONS.has(value)
|
|
113
|
+
? value
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
99
116
|
export const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
100
117
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
101
118
|
export class Latest {
|
|
@@ -191,6 +208,11 @@ export class Runtime {
|
|
|
191
208
|
views = new Map();
|
|
192
209
|
commands = new Map();
|
|
193
210
|
services = new Map();
|
|
211
|
+
/**
|
|
212
|
+
* Set when the player stops the plugin (`PluginInstance.dispose`), before
|
|
213
|
+
* the root scope is disposed: `DisposeInfo` and `plugin.stopReason`.
|
|
214
|
+
*/
|
|
215
|
+
stopping;
|
|
194
216
|
/** `plugin.app`, from the start-up handshake; `system()` asks the player. */
|
|
195
217
|
app;
|
|
196
218
|
/** `plugin.i18n.locale`, from the start-up handshake; English when unknown. */
|
package/dist/plugin/scope.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Change, Runtime } from "./runtime.ts";
|
|
2
|
-
import type { Disposable, Plugin, PluginError, Scope } from "./types.ts";
|
|
2
|
+
import type { Disposable, DisposeInfo, Plugin, PluginError, Scope, StopReason } from "./types.ts";
|
|
3
3
|
export declare class ScopeImpl implements Plugin {
|
|
4
4
|
#private;
|
|
5
5
|
readonly runtime: Runtime;
|
|
6
6
|
constructor(runtime: Runtime, parent?: ScopeImpl);
|
|
7
7
|
get id(): string;
|
|
8
8
|
get dataDirectory(): string;
|
|
9
|
+
get stopReason(): StopReason | undefined;
|
|
9
10
|
get disposed(): boolean;
|
|
10
11
|
readonly commands: import("./types.ts").Commands;
|
|
11
12
|
readonly when: import("./types.ts").When;
|
|
@@ -49,7 +50,7 @@ export declare class ScopeImpl implements Plugin {
|
|
|
49
50
|
add<T extends Disposable>(item: T): T;
|
|
50
51
|
/** Handles call this when disposed directly, so the scope forgets them. */
|
|
51
52
|
forget(item: Disposable): void;
|
|
52
|
-
onDispose(cleanup: () => void | Promise<void>): Disposable;
|
|
53
|
+
onDispose(cleanup: ((info?: DisposeInfo) => void | Promise<void>) | (() => void | Promise<void>)): Disposable;
|
|
53
54
|
onError(listener: (error: PluginError) => void): Disposable;
|
|
54
55
|
report(error: unknown): void;
|
|
55
56
|
/** Invoke plugin code on its behalf: report failures, then refresh bindings. */
|
package/dist/plugin/scope.js
CHANGED
|
@@ -39,6 +39,9 @@ export class ScopeImpl {
|
|
|
39
39
|
throw failure("unavailable", "the player provided no data directory");
|
|
40
40
|
return directory;
|
|
41
41
|
}
|
|
42
|
+
get stopReason() {
|
|
43
|
+
return this.runtime.stopping?.reason;
|
|
44
|
+
}
|
|
42
45
|
get disposed() {
|
|
43
46
|
return this.#disposed || (this.#parent?.disposed ?? false);
|
|
44
47
|
}
|
|
@@ -102,7 +105,12 @@ export class ScopeImpl {
|
|
|
102
105
|
return;
|
|
103
106
|
done = true;
|
|
104
107
|
this.forget(item);
|
|
105
|
-
|
|
108
|
+
const stopping = this.runtime.stopping;
|
|
109
|
+
await cleanup(Object.freeze(stopping
|
|
110
|
+
? stopping.reason === undefined
|
|
111
|
+
? { stopping: true }
|
|
112
|
+
: { stopping: true, reason: stopping.reason }
|
|
113
|
+
: { stopping: false }));
|
|
106
114
|
},
|
|
107
115
|
};
|
|
108
116
|
return this.add(item);
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { SubtitleState } from "../subtitles.ts";
|
|
|
18
18
|
import { VIDEO_PROCESSING_LIMITS } from "../processing.ts";
|
|
19
19
|
import type { RenderingMode, RenderingPreference, RenderingReason, RenderSettings, UpscalingDecodingCause, UpscalingPlugin, UpscalingReason, UpscalingVideoSize, ViewSettings } from "./types.ts";
|
|
20
20
|
import type { MediaJob } from "../media.ts";
|
|
21
|
-
import type { LanguagePreference, Locale, Plugin, TemplateArgs, TextValue, ThemeId } from "./types.ts";
|
|
21
|
+
import type { LanguagePreference, Locale, Plugin, StopReason, TemplateArgs, TextValue, ThemeId } from "./types.ts";
|
|
22
22
|
import { LOCALES, LIST_SEPARATORS, TEXT_LIMITS } from "../i18n.ts";
|
|
23
23
|
/** Throw one of these from `host.handle(...)` to simulate a definite refusal. */
|
|
24
24
|
export { HostError };
|
|
@@ -1351,6 +1351,10 @@ export declare class TestHost implements Transport {
|
|
|
1351
1351
|
* or switches to software rendering (`unsupported`, which also sets
|
|
1352
1352
|
* `videoRender.gpu` to false and `rendering.reason` to `window-fallback`
|
|
1353
1353
|
* with `message`). Pushed only when this plugin is the owner.
|
|
1354
|
+
* `previous-crash` (SDK 1.5.54) is a shortcut that only sets the handle's
|
|
1355
|
+
* status and reaches `onError`: the real player never pushes it, it
|
|
1356
|
+
* refuses the `set` request itself. To test that, set
|
|
1357
|
+
* {@link TestHost.previousCrash}.
|
|
1354
1358
|
*
|
|
1355
1359
|
* @example Make a shader fail to compile
|
|
1356
1360
|
* The plugin's render options report `failed`, as when mpv rejects a
|
|
@@ -1372,7 +1376,7 @@ export declare class TestHost implements Transport {
|
|
|
1372
1376
|
* @capability None.
|
|
1373
1377
|
* @since 1.0.0
|
|
1374
1378
|
*/
|
|
1375
|
-
failVideoRender(code: "failed" | "unsupported", message: string): void;
|
|
1379
|
+
failVideoRender(code: "failed" | "unsupported" | "previous-crash", message: string): void;
|
|
1376
1380
|
/**
|
|
1377
1381
|
* The shader channel (`plugin.video.shaders(id)`, SDK 1.5.39): the lists
|
|
1378
1382
|
* the player holds for this plugin's handles by id (the real paths, after
|
|
@@ -1405,6 +1409,12 @@ export declare class TestHost implements Transport {
|
|
|
1405
1409
|
* rendering (`unsupported`; use {@link TestHost.failVideoRender}, which
|
|
1406
1410
|
* also tells the handles). Pushed only to a handle that holds shaders.
|
|
1407
1411
|
*
|
|
1412
|
+
* `code` (SDK 1.5.54, default `failed`) is the error code pushed.
|
|
1413
|
+
* `previous-crash` is a shortcut that only sets the handle's status and
|
|
1414
|
+
* reaches `onError`: the real player (0.6.3) never pushes it, it refuses
|
|
1415
|
+
* the `set` request itself after an abnormal end. To test that, set
|
|
1416
|
+
* {@link TestHost.previousCrash}.
|
|
1417
|
+
*
|
|
1408
1418
|
* @capability None.
|
|
1409
1419
|
*
|
|
1410
1420
|
* @example Make the plugin's shader fail to compile
|
|
@@ -1430,7 +1440,37 @@ export declare class TestHost implements Transport {
|
|
|
1430
1440
|
*
|
|
1431
1441
|
* @since 1.5.39
|
|
1432
1442
|
*/
|
|
1433
|
-
failVideoShaders(id: string, message: string): void;
|
|
1443
|
+
failVideoShaders(id: string, message: string, code?: "failed" | "previous-crash"): void;
|
|
1444
|
+
/**
|
|
1445
|
+
* Play a run after an abnormal end with plugin shaders on (player 0.6.3):
|
|
1446
|
+
* while set, a non-empty `video.shaders` list and a render options
|
|
1447
|
+
* `set` with a non-empty `shaders` list are refused with
|
|
1448
|
+
* `previous-crash` and this message; clearing still works. `null`
|
|
1449
|
+
* (default): shaders apply.
|
|
1450
|
+
*
|
|
1451
|
+
* @example Check that the plugin stops retrying while shaders are off
|
|
1452
|
+
* The plugin's shader handle turns `failed` with `previous-crash`, as
|
|
1453
|
+
* in the player after a crash while plugin shaders were drawing. The
|
|
1454
|
+
* player refuses the list itself (and shows its own OSD line), so the
|
|
1455
|
+
* plugin should report it once and not set the list again.
|
|
1456
|
+
* ```ts
|
|
1457
|
+
* import assert from "node:assert/strict";
|
|
1458
|
+
* import { join } from "node:path";
|
|
1459
|
+
* import { writeFileSync } from "node:fs";
|
|
1460
|
+
* declare const host: TestHost;
|
|
1461
|
+
* // ---cut---
|
|
1462
|
+
* host.previousCrash = "plugin shaders are off for this start";
|
|
1463
|
+
* const file = join(plugin.dataDirectory, "sharpen.glsl");
|
|
1464
|
+
* writeFileSync(file, "//!HOOK MAIN\n");
|
|
1465
|
+
* const shaders = plugin.video.shaders("sharpen");
|
|
1466
|
+
* shaders.set([file]); // refused: the previous run ended abnormally
|
|
1467
|
+
* await host.settle();
|
|
1468
|
+
* assert.equal(shaders.status.state, "failed");
|
|
1469
|
+
* ```
|
|
1470
|
+
*
|
|
1471
|
+
* @since 1.5.54
|
|
1472
|
+
*/
|
|
1473
|
+
previousCrash: string | null;
|
|
1434
1474
|
/**
|
|
1435
1475
|
* The player's processing (filter) state. Wire-level; may change in minor
|
|
1436
1476
|
* versions. `resources` holds each `video.filter` / `audio.filter` chain the
|
|
@@ -1942,6 +1982,11 @@ export declare class TestHost implements Transport {
|
|
|
1942
1982
|
/** Start time in seconds. */
|
|
1943
1983
|
start: number;
|
|
1944
1984
|
}[];
|
|
1985
|
+
/**
|
|
1986
|
+
* @internal `startPlugin`'s `disconnect()`: refuse every request, the
|
|
1987
|
+
* ones in flight included, as the runner does once the player is gone.
|
|
1988
|
+
*/
|
|
1989
|
+
loseConnection(): void;
|
|
1945
1990
|
/**
|
|
1946
1991
|
* `plugin.dataDirectory`: a fresh empty temporary directory, removed with
|
|
1947
1992
|
* the host on `stop()`. `plugin.storage.json` reads and writes real files
|
|
@@ -2754,7 +2799,11 @@ export declare class TestHost implements Transport {
|
|
|
2754
2799
|
* @returns `host`: the {@link TestHost}; `plugin`: the object passed to
|
|
2755
2800
|
* `activate` (call the plugin API on it directly); `stop()`: dispose the
|
|
2756
2801
|
* plugin like the player stopping it (scopes released, stores flushed),
|
|
2757
|
-
* wait until the host is idle and remove the host's temporary files
|
|
2802
|
+
* wait until the host is idle and remove the host's temporary files;
|
|
2803
|
+
* `stop({ reason })` (SDK 1.5.54): `reason` is the `StopReason` the player
|
|
2804
|
+
* sends; `disconnect()` (SDK 1.5.54) stops it as when the player is lost
|
|
2805
|
+
* (`host-lost`, requests refused with `disabled`). Removing the
|
|
2806
|
+
* temporary files never fails a test.
|
|
2758
2807
|
*
|
|
2759
2808
|
* @example Test that a setting survives a restart
|
|
2760
2809
|
* The test turns on the 10-step option, stops the plugin, starts it again
|
|
@@ -2843,6 +2892,25 @@ export declare function startPlugin(activate: (plugin: Plugin) => unknown, optio
|
|
|
2843
2892
|
host: TestHost;
|
|
2844
2893
|
/** The `plugin` object passed to `activate`. */
|
|
2845
2894
|
plugin: Plugin;
|
|
2846
|
-
/**
|
|
2847
|
-
|
|
2895
|
+
/**
|
|
2896
|
+
* Stop the plugin, wait for the host, and remove temporary files.
|
|
2897
|
+
* `stop({ reason })` (SDK 1.5.54): `reason` is the reason the player
|
|
2898
|
+
* gives (`plugin.stopReason`, the `onDispose` cleanups'
|
|
2899
|
+
* `info.reason`); `stop()`: none (`undefined`), as with players up to
|
|
2900
|
+
* 0.6.3. It still takes no declared parameter, so it can be passed as
|
|
2901
|
+
* a callback (`t.after(stop)`, `promise.then(stop)`).
|
|
2902
|
+
*/
|
|
2903
|
+
stop: {
|
|
2904
|
+
(): Promise<void>;
|
|
2905
|
+
(options: {
|
|
2906
|
+
reason?: StopReason;
|
|
2907
|
+
}): Promise<void>;
|
|
2908
|
+
};
|
|
2909
|
+
/**
|
|
2910
|
+
* Lose the player (SDK 1.5.54): as when it crashes or is killed, the
|
|
2911
|
+
* runner stops the plugin with reason `host-lost`; requests still
|
|
2912
|
+
* waiting for their answer and every later request are refused with
|
|
2913
|
+
* `disabled`. Then waits and removes the temporary files like `stop`.
|
|
2914
|
+
*/
|
|
2915
|
+
disconnect(): Promise<void>;
|
|
2848
2916
|
}>;
|
package/dist/plugin/testing.js
CHANGED
|
@@ -1510,6 +1510,10 @@ export class TestHost {
|
|
|
1510
1510
|
* or switches to software rendering (`unsupported`, which also sets
|
|
1511
1511
|
* `videoRender.gpu` to false and `rendering.reason` to `window-fallback`
|
|
1512
1512
|
* with `message`). Pushed only when this plugin is the owner.
|
|
1513
|
+
* `previous-crash` (SDK 1.5.54) is a shortcut that only sets the handle's
|
|
1514
|
+
* status and reaches `onError`: the real player never pushes it, it
|
|
1515
|
+
* refuses the `set` request itself. To test that, set
|
|
1516
|
+
* {@link TestHost.previousCrash}.
|
|
1513
1517
|
*
|
|
1514
1518
|
* @example Make a shader fail to compile
|
|
1515
1519
|
* The plugin's render options report `failed`, as when mpv rejects a
|
|
@@ -1581,6 +1585,12 @@ export class TestHost {
|
|
|
1581
1585
|
* rendering (`unsupported`; use {@link TestHost.failVideoRender}, which
|
|
1582
1586
|
* also tells the handles). Pushed only to a handle that holds shaders.
|
|
1583
1587
|
*
|
|
1588
|
+
* `code` (SDK 1.5.54, default `failed`) is the error code pushed.
|
|
1589
|
+
* `previous-crash` is a shortcut that only sets the handle's status and
|
|
1590
|
+
* reaches `onError`: the real player (0.6.3) never pushes it, it refuses
|
|
1591
|
+
* the `set` request itself after an abnormal end. To test that, set
|
|
1592
|
+
* {@link TestHost.previousCrash}.
|
|
1593
|
+
*
|
|
1584
1594
|
* @capability None.
|
|
1585
1595
|
*
|
|
1586
1596
|
* @example Make the plugin's shader fail to compile
|
|
@@ -1606,14 +1616,44 @@ export class TestHost {
|
|
|
1606
1616
|
*
|
|
1607
1617
|
* @since 1.5.39
|
|
1608
1618
|
*/
|
|
1609
|
-
failVideoShaders(id, message) {
|
|
1619
|
+
failVideoShaders(id, message, code = "failed") {
|
|
1610
1620
|
if ((this.videoShaders.handles.get(id)?.length ?? 0) > 0)
|
|
1611
1621
|
this.emit("video.shaders.status", {
|
|
1612
1622
|
id,
|
|
1613
1623
|
state: "failed",
|
|
1614
|
-
error: { code
|
|
1624
|
+
error: { code, message },
|
|
1615
1625
|
});
|
|
1616
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Play a run after an abnormal end with plugin shaders on (player 0.6.3):
|
|
1629
|
+
* while set, a non-empty `video.shaders` list and a render options
|
|
1630
|
+
* `set` with a non-empty `shaders` list are refused with
|
|
1631
|
+
* `previous-crash` and this message; clearing still works. `null`
|
|
1632
|
+
* (default): shaders apply.
|
|
1633
|
+
*
|
|
1634
|
+
* @example Check that the plugin stops retrying while shaders are off
|
|
1635
|
+
* The plugin's shader handle turns `failed` with `previous-crash`, as
|
|
1636
|
+
* in the player after a crash while plugin shaders were drawing. The
|
|
1637
|
+
* player refuses the list itself (and shows its own OSD line), so the
|
|
1638
|
+
* plugin should report it once and not set the list again.
|
|
1639
|
+
* ```ts
|
|
1640
|
+
* import assert from "node:assert/strict";
|
|
1641
|
+
* import { join } from "node:path";
|
|
1642
|
+
* import { writeFileSync } from "node:fs";
|
|
1643
|
+
* declare const host: TestHost;
|
|
1644
|
+
* // ---cut---
|
|
1645
|
+
* host.previousCrash = "plugin shaders are off for this start";
|
|
1646
|
+
* const file = join(plugin.dataDirectory, "sharpen.glsl");
|
|
1647
|
+
* writeFileSync(file, "//!HOOK MAIN\n");
|
|
1648
|
+
* const shaders = plugin.video.shaders("sharpen");
|
|
1649
|
+
* shaders.set([file]); // refused: the previous run ended abnormally
|
|
1650
|
+
* await host.settle();
|
|
1651
|
+
* assert.equal(shaders.status.state, "failed");
|
|
1652
|
+
* ```
|
|
1653
|
+
*
|
|
1654
|
+
* @since 1.5.54
|
|
1655
|
+
*/
|
|
1656
|
+
previousCrash = null;
|
|
1617
1657
|
/**
|
|
1618
1658
|
* The player's processing (filter) state. Wire-level; may change in minor
|
|
1619
1659
|
* versions. `resources` holds each `video.filter` / `audio.filter` chain the
|
|
@@ -2099,6 +2139,25 @@ export class TestHost {
|
|
|
2099
2139
|
#overrides = new Map();
|
|
2100
2140
|
#listeners = new Map();
|
|
2101
2141
|
#inFlight = 0;
|
|
2142
|
+
/** `disconnect()` of `startPlugin`: the player is gone. */
|
|
2143
|
+
#lost = false;
|
|
2144
|
+
#lose = () => { };
|
|
2145
|
+
/** Rejects when the player is lost: requests in flight fail with it. */
|
|
2146
|
+
#gone = (() => {
|
|
2147
|
+
const gone = new Promise((_, reject) => {
|
|
2148
|
+
this.#lose = reject;
|
|
2149
|
+
});
|
|
2150
|
+
gone.catch(() => { });
|
|
2151
|
+
return gone;
|
|
2152
|
+
})();
|
|
2153
|
+
/**
|
|
2154
|
+
* @internal `startPlugin`'s `disconnect()`: refuse every request, the
|
|
2155
|
+
* ones in flight included, as the runner does once the player is gone.
|
|
2156
|
+
*/
|
|
2157
|
+
loseConnection() {
|
|
2158
|
+
this.#lost = true;
|
|
2159
|
+
this.#lose(new HostError("disabled", "plugin stopped"));
|
|
2160
|
+
}
|
|
2102
2161
|
#next = { snapshot: 0, job: 0, producer: 0, source: 0, resolution: 0 };
|
|
2103
2162
|
#sourceBook = new Map();
|
|
2104
2163
|
/** The pending resolutions' stamps and operations (beside `resolutions`). */
|
|
@@ -2573,6 +2632,10 @@ export class TestHost {
|
|
|
2573
2632
|
* override from {@link TestHost.handle} or the default behaviour.
|
|
2574
2633
|
*/
|
|
2575
2634
|
async request(method, params = {}) {
|
|
2635
|
+
// After `disconnect()`: refused as the runner refuses them once the
|
|
2636
|
+
// player is gone.
|
|
2637
|
+
if (this.#lost)
|
|
2638
|
+
throw new HostError("disabled", "plugin is stopping");
|
|
2576
2639
|
const record = (params ?? {});
|
|
2577
2640
|
// The plugin runner refuses a request over the message limit before
|
|
2578
2641
|
// sending it (runtime/runner.ts): the player never sees it.
|
|
@@ -2583,10 +2646,15 @@ export class TestHost {
|
|
|
2583
2646
|
this.#inFlight++;
|
|
2584
2647
|
try {
|
|
2585
2648
|
await tick();
|
|
2649
|
+
if (this.#lost)
|
|
2650
|
+
throw new HostError("disabled", "plugin stopped");
|
|
2586
2651
|
const handler = this.#overrides.get(method) ?? this.#defaults[method];
|
|
2587
2652
|
if (!handler)
|
|
2588
2653
|
throw new HostError("unsupported", `test host has no ${method}`);
|
|
2589
|
-
|
|
2654
|
+
const work = Promise.resolve(handler(record));
|
|
2655
|
+
// `disconnect()` fails the requests still waiting, as the runner does.
|
|
2656
|
+
work.catch(() => { });
|
|
2657
|
+
return (await Promise.race([work, this.#gone]));
|
|
2590
2658
|
}
|
|
2591
2659
|
finally {
|
|
2592
2660
|
this.#inFlight--;
|
|
@@ -3424,7 +3492,20 @@ export class TestHost {
|
|
|
3424
3492
|
close() {
|
|
3425
3493
|
for (const producer of this.producers.values())
|
|
3426
3494
|
producer.server.close();
|
|
3427
|
-
|
|
3495
|
+
// Cleanup never fails a test: on Windows a file a closing handle still
|
|
3496
|
+
// holds makes the removal fail now and then (`ENOTEMPTY`, `EBUSY`,
|
|
3497
|
+
// `EPERM`); retry, then leave the directory to the system's cleanup.
|
|
3498
|
+
try {
|
|
3499
|
+
rmSync(this.#scratch, {
|
|
3500
|
+
recursive: true,
|
|
3501
|
+
force: true,
|
|
3502
|
+
maxRetries: 5,
|
|
3503
|
+
retryDelay: 100,
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
catch {
|
|
3507
|
+
// Left in the temporary directory.
|
|
3508
|
+
}
|
|
3428
3509
|
}
|
|
3429
3510
|
// ------------------------------------------------------------ simulated host
|
|
3430
3511
|
#intent(intent) {
|
|
@@ -4575,6 +4656,12 @@ export class TestHost {
|
|
|
4575
4656
|
return null;
|
|
4576
4657
|
},
|
|
4577
4658
|
"video.render.set": (p) => {
|
|
4659
|
+
// The player's crash guard comes first (`processing_video.rs`).
|
|
4660
|
+
const listed = p.values?.shaders;
|
|
4661
|
+
if (this.previousCrash !== null &&
|
|
4662
|
+
Array.isArray(listed) &&
|
|
4663
|
+
listed.length > 0)
|
|
4664
|
+
throw new HostError("previous-crash", this.previousCrash);
|
|
4578
4665
|
if (!this.videoRender.gpu)
|
|
4579
4666
|
throw new HostError("unsupported", "render options need GPU rendering; the player renders in software");
|
|
4580
4667
|
const owner = this.videoRender.owner;
|
|
@@ -4610,6 +4697,9 @@ export class TestHost {
|
|
|
4610
4697
|
if (listProblem?.startsWith("at most"))
|
|
4611
4698
|
throw new HostError("limit", listProblem);
|
|
4612
4699
|
const paths = list.map((path) => ownFile(path, this.fileRoots, "shader", VIDEO_PROCESSING_LIMITS.shaderBytes));
|
|
4700
|
+
// The player's crash guard, after the files are checked.
|
|
4701
|
+
if (paths.length > 0 && this.previousCrash !== null)
|
|
4702
|
+
throw new HostError("previous-crash", this.previousCrash);
|
|
4613
4703
|
if (paths.length > 0 && !this.videoRender.gpu)
|
|
4614
4704
|
throw new HostError("unsupported", "shaders need GPU rendering; the player renders in software");
|
|
4615
4705
|
const mine = [...this.videoShaders.handles]
|
|
@@ -5455,7 +5545,11 @@ function tellResidency(host) {
|
|
|
5455
5545
|
* @returns `host`: the {@link TestHost}; `plugin`: the object passed to
|
|
5456
5546
|
* `activate` (call the plugin API on it directly); `stop()`: dispose the
|
|
5457
5547
|
* plugin like the player stopping it (scopes released, stores flushed),
|
|
5458
|
-
* wait until the host is idle and remove the host's temporary files
|
|
5548
|
+
* wait until the host is idle and remove the host's temporary files;
|
|
5549
|
+
* `stop({ reason })` (SDK 1.5.54): `reason` is the `StopReason` the player
|
|
5550
|
+
* sends; `disconnect()` (SDK 1.5.54) stops it as when the player is lost
|
|
5551
|
+
* (`host-lost`, requests refused with `disabled`). Removing the
|
|
5552
|
+
* temporary files never fails a test.
|
|
5459
5553
|
*
|
|
5460
5554
|
* @example Test that a setting survives a restart
|
|
5461
5555
|
* The test turns on the 10-step option, stops the plugin, starts it again
|
|
@@ -5552,14 +5646,43 @@ export async function startPlugin(activate, options = {}) {
|
|
|
5552
5646
|
}
|
|
5553
5647
|
await host.settle();
|
|
5554
5648
|
tellResidency(host);
|
|
5649
|
+
// Zero declared parameters (`stop.length === 0`) and a `()` overload, so
|
|
5650
|
+
// `t.after(stop)`, `promise.then(stop)` and done-callback test runners
|
|
5651
|
+
// see the same function as before 1.5.54.
|
|
5652
|
+
const stop = async (...args) => {
|
|
5653
|
+
// Only an object with `reason` counts (a value `then` or a test
|
|
5654
|
+
// runner passes is ignored; an unknown reason reads as none).
|
|
5655
|
+
const first = args[0];
|
|
5656
|
+
const reason = typeof first === "object" && first !== null && "reason" in first
|
|
5657
|
+
? first.reason
|
|
5658
|
+
: undefined;
|
|
5659
|
+
await instance.dispose({ reason });
|
|
5660
|
+
await host.settle();
|
|
5661
|
+
host.close();
|
|
5662
|
+
};
|
|
5555
5663
|
return {
|
|
5556
5664
|
/** The test host the plugin talks to. */
|
|
5557
5665
|
host,
|
|
5558
5666
|
/** The `plugin` object passed to `activate`. */
|
|
5559
5667
|
plugin: instance.plugin,
|
|
5560
|
-
/**
|
|
5561
|
-
|
|
5562
|
-
|
|
5668
|
+
/**
|
|
5669
|
+
* Stop the plugin, wait for the host, and remove temporary files.
|
|
5670
|
+
* `stop({ reason })` (SDK 1.5.54): `reason` is the reason the player
|
|
5671
|
+
* gives (`plugin.stopReason`, the `onDispose` cleanups'
|
|
5672
|
+
* `info.reason`); `stop()`: none (`undefined`), as with players up to
|
|
5673
|
+
* 0.6.3. It still takes no declared parameter, so it can be passed as
|
|
5674
|
+
* a callback (`t.after(stop)`, `promise.then(stop)`).
|
|
5675
|
+
*/
|
|
5676
|
+
stop,
|
|
5677
|
+
/**
|
|
5678
|
+
* Lose the player (SDK 1.5.54): as when it crashes or is killed, the
|
|
5679
|
+
* runner stops the plugin with reason `host-lost`; requests still
|
|
5680
|
+
* waiting for their answer and every later request are refused with
|
|
5681
|
+
* `disabled`. Then waits and removes the temporary files like `stop`.
|
|
5682
|
+
*/
|
|
5683
|
+
async disconnect() {
|
|
5684
|
+
host.loseConnection();
|
|
5685
|
+
await instance.dispose({ reason: "host-lost" });
|
|
5563
5686
|
await host.settle();
|
|
5564
5687
|
host.close();
|
|
5565
5688
|
},
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -123,6 +123,7 @@ export interface Disposable {
|
|
|
123
123
|
* | `resolution` | A file or web resolver's result could not be opened; `message` has the reason. |
|
|
124
124
|
* | `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. |
|
|
125
125
|
* | `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°. |
|
|
126
|
+
* | `previous-crash` | {@link VideoShaders}, {@link RenderOptions} `shaders` (player 0.6.3): plugin shaders are off because the player ended abnormally while they were drawing; clearing still works. Do not retry while refused (the player shows its own OSD line). |
|
|
126
127
|
* | `source-unavailable` | The source is not ready (still loading, or its time or native binding is not available yet). |
|
|
127
128
|
* | `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. |
|
|
128
129
|
*
|
|
@@ -416,10 +417,36 @@ export interface Scope extends Disposable {
|
|
|
416
417
|
* plugin.onDispose(() => clearInterval(timer));
|
|
417
418
|
* ```
|
|
418
419
|
*
|
|
420
|
+
* @example Save a cache at once when the player is lost
|
|
421
|
+
* The plugin writes its cache file at once when the player crashed or
|
|
422
|
+
* was killed, instead of waiting for the next periodic save. The cleanup
|
|
423
|
+
* may get a {@link DisposeInfo}: `stopping` says the plugin itself stops
|
|
424
|
+
* (not a child scope), `reason` why (planned for player 0.6.4). Players
|
|
425
|
+
* whose SDK is older than 1.5.54 call the cleanup without an argument,
|
|
426
|
+
* so read it with `?.`.
|
|
427
|
+
* ```ts
|
|
428
|
+
* declare function saveCacheNow(): void;
|
|
429
|
+
* // ---cut---
|
|
430
|
+
* plugin.onDispose((info) => {
|
|
431
|
+
* // The player is gone: ask it nothing, keep what is local.
|
|
432
|
+
* if (info?.reason === "host-lost") saveCacheNow();
|
|
433
|
+
* });
|
|
434
|
+
* ```
|
|
435
|
+
*
|
|
436
|
+
* @remarks
|
|
437
|
+
* From SDK 1.5.54 in the player (and in `TestHost`) the cleanup is called
|
|
438
|
+
* with one argument, a {@link DisposeInfo}: whether the plugin stops, and
|
|
439
|
+
* why. A player whose own SDK is older calls it without one (`info` is
|
|
440
|
+
* `undefined`). A cleanup that takes no argument works as before; a
|
|
441
|
+
* function with optional parameters of its own that is passed directly
|
|
442
|
+
* (`onDispose(controller.abort.bind(controller))`) now receives the
|
|
443
|
+
* `DisposeInfo` as its first argument: wrap it (`() => controller.abort()`)
|
|
444
|
+
* when that matters.
|
|
445
|
+
*
|
|
419
446
|
* @capability None.
|
|
420
447
|
* @since 1.0.0
|
|
421
448
|
*/
|
|
422
|
-
onDispose(cleanup: () => void | Promise<void>): Disposable;
|
|
449
|
+
onDispose(cleanup: ((info?: DisposeInfo) => void | Promise<void>) | (() => void | Promise<void>)): Disposable;
|
|
423
450
|
/**
|
|
424
451
|
* Errors thrown by handlers that the SDK invoked (command `run`, window and
|
|
425
452
|
* panel callbacks, `onChange` listeners) in this scope. Without a listener
|
|
@@ -547,6 +574,65 @@ export interface Plugin extends Scope {
|
|
|
547
574
|
* none. See [How a plugin runs](../../docs/explanation/plugins.md#the-data-directory).
|
|
548
575
|
*/
|
|
549
576
|
readonly dataDirectory: string;
|
|
577
|
+
/**
|
|
578
|
+
* Why the player stops this plugin (SDK 1.5.54; the reasons are planned
|
|
579
|
+
* for player 0.6.4): set when the stop begins, before any
|
|
580
|
+
* {@link Scope.onDispose} cleanup runs. `undefined` while the plugin
|
|
581
|
+
* runs, and for every stop by an older player (one whose own SDK is older
|
|
582
|
+
* than 1.5.54 does not have the property at all). See {@link StopReason}.
|
|
583
|
+
*/
|
|
584
|
+
readonly stopReason: StopReason | undefined;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Why the player stopped the plugin ({@link DisposeInfo.reason},
|
|
588
|
+
* {@link Plugin.stopReason}).
|
|
589
|
+
*
|
|
590
|
+
* - `quit`: the player quits.
|
|
591
|
+
* - `disable`: the user disabled the plugin.
|
|
592
|
+
* - `uninstall`: the user uninstalls it.
|
|
593
|
+
* - `reload`: it is reloaded (from the plugin manager, or a development
|
|
594
|
+
* load with `--plugin`).
|
|
595
|
+
* - `upgrade`: a new version replaces it.
|
|
596
|
+
* - `idle`: lazy activation stops it after a while without use; its next
|
|
597
|
+
* use starts it again ([Idle plugins](../../docs/explanation/plugins.md#idle-plugins-are-stopped)).
|
|
598
|
+
* - `failed`: the player stops it for a fault (planned to cover an
|
|
599
|
+
* unreadable or oversized line, a malformed request, an input queue that
|
|
600
|
+
* overflowed, an uncaught exception in the shared process).
|
|
601
|
+
* - `host-lost`: the plugin's runner saw its pipe to the player close
|
|
602
|
+
* without a stop: the player crashed or was killed. Requests are refused
|
|
603
|
+
* with `disabled`; clean up locally only.
|
|
604
|
+
*
|
|
605
|
+
* Planned for player 0.6.4: the player sends the reason with
|
|
606
|
+
* `runtime.stop` and its runner reports `host-lost`; the exact list may
|
|
607
|
+
* still change with that release. Players up to 0.6.3 give no reason for
|
|
608
|
+
* any stop, a lost player included (`undefined`); so does a stop that
|
|
609
|
+
* does not come from the player (a `TestHost` `stop()` without a reason).
|
|
610
|
+
* A value this SDK does not know reads as `undefined`.
|
|
611
|
+
*
|
|
612
|
+
* @player 0.6.4 (planned); older players give no reason (`undefined`).
|
|
613
|
+
* @since 1.5.54
|
|
614
|
+
* @category Plugin
|
|
615
|
+
*/
|
|
616
|
+
export type StopReason = "quit" | "disable" | "uninstall" | "reload" | "upgrade" | "idle" | "failed" | "host-lost";
|
|
617
|
+
/**
|
|
618
|
+
* What a {@link Scope.onDispose} cleanup is told (SDK 1.5.54). A player
|
|
619
|
+
* whose own SDK is older passes nothing, so the cleanup's parameter is
|
|
620
|
+
* optional.
|
|
621
|
+
*
|
|
622
|
+
* @since 1.5.54
|
|
623
|
+
* @category Plugin
|
|
624
|
+
*/
|
|
625
|
+
export interface DisposeInfo {
|
|
626
|
+
/**
|
|
627
|
+
* `true` when the plugin itself stops (the player stops it), `false`
|
|
628
|
+
* when the plugin's own code disposed this scope or one of its parents.
|
|
629
|
+
*/
|
|
630
|
+
readonly stopping: boolean;
|
|
631
|
+
/**
|
|
632
|
+
* Why the player stops the plugin (planned for player 0.6.4); `undefined` while
|
|
633
|
+
* `stopping` is false, and for every stop by an older player.
|
|
634
|
+
*/
|
|
635
|
+
readonly reason?: StopReason;
|
|
550
636
|
}
|
|
551
637
|
/**
|
|
552
638
|
* The state of a command's menu item, as the player shows it.
|
|
@@ -6716,7 +6802,13 @@ export interface RenderSettings {
|
|
|
6716
6802
|
* the player's first answer to `render()` (reported to `onError` too), and so is
|
|
6717
6803
|
* every `set`. Single owner, like {@link VideoView}. `set` never throws; the
|
|
6718
6804
|
* result shows in `status`. For shaders that several plugins add at the
|
|
6719
|
-
* same time, use {@link VideoShaders} (SDK 1.5.39).
|
|
6805
|
+
* same time, use {@link VideoShaders} (SDK 1.5.39). After a run of the
|
|
6806
|
+
* player that ended abnormally while plugin shaders were drawing, a `set`
|
|
6807
|
+
* whose merged values hold a non-empty `shaders` list is refused with
|
|
6808
|
+
* `previous-crash` (player 0.6.3; the rules are on {@link VideoShaders}).
|
|
6809
|
+
* Every `set` sends all the values set so far, the refused `shaders`
|
|
6810
|
+
* included, so include `shaders: []` in the next `set` to go on without
|
|
6811
|
+
* them.
|
|
6720
6812
|
*
|
|
6721
6813
|
* @capability processing.video, playback.read and events (like
|
|
6722
6814
|
* {@link VideoFilter})
|
|
@@ -6745,7 +6837,15 @@ export interface RenderOptions extends Disposable {
|
|
|
6745
6837
|
* `failed` with `unsupported` while the player renders in software;
|
|
6746
6838
|
* `failed` with `failed` when mpv cannot compile a shader (pushed later;
|
|
6747
6839
|
* while other plugins' {@link VideoShaders} are on too, the player
|
|
6748
|
-
* cannot tell whose shader failed and says so in the message)
|
|
6840
|
+
* cannot tell whose shader failed and says so in the message);
|
|
6841
|
+
* `failed` with `previous-crash` (player 0.6.3) when a `set` with a
|
|
6842
|
+
* non-empty `shaders` list comes after a run of the player that ended
|
|
6843
|
+
* abnormally while plugin shaders were drawing (the rules are the same
|
|
6844
|
+
* as for {@link VideoShaders}). The handle keeps the refused list among
|
|
6845
|
+
* its values and every `set` sends them all, so a later `set` without
|
|
6846
|
+
* `shaders` is refused again: include `shaders: []` in it
|
|
6847
|
+
* (`set({ shaders: [], toneMapping: "bt.2390" })`); `dispose` always
|
|
6848
|
+
* works.
|
|
6749
6849
|
*/
|
|
6750
6850
|
readonly status: FilterStatus;
|
|
6751
6851
|
/**
|
|
@@ -6805,6 +6905,20 @@ export interface RenderOptions extends Disposable {
|
|
|
6805
6905
|
* Players before 0.5.20 refuse the request (the handle is `failed`): declare
|
|
6806
6906
|
* `"minPlayerVersion": "0.5.20"`.
|
|
6807
6907
|
*
|
|
6908
|
+
* **After an abnormal end** (player 0.6.3): when the previous run of the
|
|
6909
|
+
* player ended while plugin shaders were drawing (a crash, the process
|
|
6910
|
+
* being ended, a lost GPU device), the player refuses every non-empty
|
|
6911
|
+
* list of every plugin with `previous-crash`, its message naming the
|
|
6912
|
+
* plugins whose shaders were on: one such end keeps them off for the next
|
|
6913
|
+
* start only; two within a day keep them off until the GPU, its driver or
|
|
6914
|
+
* the player changes, or for 7 days. Clearing (`[]`, `clear`,
|
|
6915
|
+
* `dispose`) still works. The player shows one OSD line about it itself;
|
|
6916
|
+
* the handle is `failed` with `previous-crash` and the SDK does not
|
|
6917
|
+
* retry it. Do not set the list again in a loop while it is refused, and
|
|
6918
|
+
* do not show an OSD line of your own for it; a later start of the player
|
|
6919
|
+
* applies lists again once the guard lets it. Players before 0.6.3 never
|
|
6920
|
+
* refuse with it.
|
|
6921
|
+
*
|
|
6808
6922
|
* @example Add a sharpening shader the plugin ships
|
|
6809
6923
|
* The video looks sharper on the GPU renderer; under software rendering
|
|
6810
6924
|
* the OSD says why it is not. The shader file lives in the plugin's
|
|
@@ -6835,7 +6949,8 @@ export interface VideoShaders extends Disposable {
|
|
|
6835
6949
|
*
|
|
6836
6950
|
* @remarks
|
|
6837
6951
|
* Never throws: a refused list shows in `status` (`invalid`, `missing`,
|
|
6838
|
-
* `limit`, `unsupported
|
|
6952
|
+
* `limit`, `unsupported`, and `previous-crash` from player 0.6.3: see
|
|
6953
|
+
* the interface's remarks) and goes to {@link Scope.onError}, and what the
|
|
6839
6954
|
* player held stays.
|
|
6840
6955
|
*
|
|
6841
6956
|
* @capability processing.video
|
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.54: 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. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
|
|
4
4
|
|
|
5
5
|
## Read the documentation
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- `/API/Media`: 6 entries: CaptureOptions, Snapshot, ExportOptions, JobState, ExportJob, MediaCapture
|
|
33
33
|
- `/API/Packages`: 10 entries: PackageManifest, PackageSource, InstallOptions, PackageCandidate, PackageChangeStatus, PackageChange, PluginInfo, PluginDisplay, PluginWarning, Packages
|
|
34
34
|
- `/API/Playback`: 18 entries: MarkKind, TimelineMark, TimelineLayer, Timeline, AudioTrack, PlayerState, AudioDevice, Chapter, Conditional, Media, Scrub, StreamFrame, StreamOptions, StreamStartError, Stream,…
|
|
35
|
-
- `/API/Plugin`:
|
|
35
|
+
- `/API/Plugin`: 13 entries: Disposable, PluginError, Scope, Plugin, StopReason, DisposeInfo, WindowState, MainWindow, AppInfo, App, SystemInfo, DisplayInfo, Json
|
|
36
36
|
- `/API/Processing`: 18 entries: FilterStatus, VideoFilter, AudioFilter, ViewSettings, VideoView, RenderSettings, RenderOptions, VideoShaders, UpscalingReasonCode, UpscalingReason, UpscalingDecodingCause,…
|
|
37
37
|
- `/API/Services`: 15 entries: Clipboard, Power, ToolResult, Tools, JsonStore, Storage, SessionStorage, System, Secrets, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState,…
|
|
38
38
|
- `/API/Sources`: 7 entries: ResolverOptions, WebResolverOptions, FileResolverOptions, ResolvedStream, Resolver, Resolvers, ResolvedMedia
|