@vestra-player/sdk 1.5.36 → 1.5.38
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 +18 -0
- package/README.md +1 -0
- package/dist/api-requests.js +2 -0
- package/dist/host-capabilities.js +11 -0
- package/dist/plugin/appearance.js +11 -2
- package/dist/plugin/packages.js +3 -0
- package/dist/plugin/player.js +6 -1
- package/dist/plugin/resolvers.js +10 -2
- package/dist/plugin/runtime.d.ts +14 -1
- package/dist/plugin/runtime.js +107 -16
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -1
- package/dist/plugin/services.d.ts +7 -1
- package/dist/plugin/services.js +26 -2
- package/dist/plugin/subtitles.js +14 -2
- package/dist/plugin/testing.d.ts +72 -2
- package/dist/plugin/testing.js +119 -3
- package/dist/plugin/timeline.d.ts +1 -1
- package/dist/plugin/timeline.js +10 -2
- package/dist/plugin/types.d.ts +286 -4
- package/dist/plugin/ui.js +9 -2
- package/dist/plugin/window.d.ts +1 -1
- package/dist/plugin/window.js +10 -2
- package/dist/wire.d.ts +29 -2
- package/dist/wire.js +19 -3
- package/llms-full.txt +341 -6
- package/llms.txt +23 -22
- package/package.json +1 -1
package/dist/plugin/testing.js
CHANGED
|
@@ -268,7 +268,8 @@ const MAX_SIDEBAR_TABS = 8;
|
|
|
268
268
|
* {@link TestHost.app}, {@link TestHost.systemInfo},
|
|
269
269
|
* {@link TestHost.chapters}, {@link TestHost.audioDevices},
|
|
270
270
|
* {@link TestHost.secrets}, {@link TestHost.clipboard},
|
|
271
|
-
* {@link TestHost.tools}, {@link TestHost.rendering}
|
|
271
|
+
* {@link TestHost.tools}, {@link TestHost.rendering},
|
|
272
|
+
* {@link TestHost.upscaling}.
|
|
272
273
|
* - **Assert** on records: {@link TestHost.osd}, {@link TestHost.calls},
|
|
273
274
|
* {@link TestHost.sent}, {@link TestHost.conflicts},
|
|
274
275
|
* {@link TestHost.layout}, {@link TestHost.menus}, and many more.
|
|
@@ -332,6 +333,16 @@ export class TestHost {
|
|
|
332
333
|
calls = [];
|
|
333
334
|
/** The host events the plugin subscribed to (needs `events` etc.). */
|
|
334
335
|
subscriptions = new Set();
|
|
336
|
+
/**
|
|
337
|
+
* The subscribed events the plugin really listens to: an `onChange` and
|
|
338
|
+
* the like, or a binding that reads the state. The SDK's own copies of
|
|
339
|
+
* the state (what keeps `plugin.player.state` current) are left out.
|
|
340
|
+
* Empty means the player may stop the plugin while nothing uses it
|
|
341
|
+
* (unless it holds something else: a window, a panel, a filter…).
|
|
342
|
+
*
|
|
343
|
+
* @since 1.5.38
|
|
344
|
+
*/
|
|
345
|
+
listening = new Set();
|
|
335
346
|
/**
|
|
336
347
|
* The player's source facts (phase, position, duration, paused, rate,
|
|
337
348
|
* audio tracks, loop, ...). Wire-level shape; change it with
|
|
@@ -1622,6 +1633,60 @@ export class TestHost {
|
|
|
1622
1633
|
gpu: { available: true, reason: null },
|
|
1623
1634
|
restarts: 0,
|
|
1624
1635
|
};
|
|
1636
|
+
/**
|
|
1637
|
+
* The GPU's video upscaler behind `plugin.video.upscaling` (player
|
|
1638
|
+
* 0.5.16), answered as the player would:
|
|
1639
|
+
*
|
|
1640
|
+
* - `state()` and `set(true)` set `checked` (the player's one GPU check; a
|
|
1641
|
+
* plugin that should not wake the GPU while it starts can assert it is
|
|
1642
|
+
* still `false`) and report `available` from `reason` (`null`: it
|
|
1643
|
+
* works), this plugin's `enabled`, and `requested` with `factor` while
|
|
1644
|
+
* enabled, available and `factor` is above 1.
|
|
1645
|
+
* - `set(true)` refuses with `unsupported` and the reason's message while
|
|
1646
|
+
* `reason` is set, unless its code is `decoding`, or `rendering` with
|
|
1647
|
+
* `renderingStarting` (GPU rendering still starting): those can pass
|
|
1648
|
+
* during the run, so the player keeps the request. Nothing changes when
|
|
1649
|
+
* it refuses.
|
|
1650
|
+
* - `set(false)` always succeeds and does not check the GPU: before any
|
|
1651
|
+
* check it reports `available: false` with the reason `unchecked`.
|
|
1652
|
+
*
|
|
1653
|
+
* Inject other answers with `host.handle("video.upscaling.set", …)`. The
|
|
1654
|
+
* defaults are a player where upscaling works and a 1080p video shown
|
|
1655
|
+
* full screen on a 4K display (`factor` 2), whatever `systemInfo` says.
|
|
1656
|
+
*
|
|
1657
|
+
* @example Test the menu item on a computer without an RTX GPU
|
|
1658
|
+
* The plugin's upscaling item is greyed out with the player's reason after
|
|
1659
|
+
* the user first picks it. Starting the plugin checks nothing (`checked`
|
|
1660
|
+
* stays false, so no GPU wakes up); the first pick reads the state, and
|
|
1661
|
+
* `set(true)` would reject with `unsupported`, as in the player.
|
|
1662
|
+
* ```ts
|
|
1663
|
+
* import assert from "node:assert/strict";
|
|
1664
|
+
* import { startPlugin, TestHost } from "@vestra-player/sdk/testing";
|
|
1665
|
+
* import activate from "../main.ts";
|
|
1666
|
+
*
|
|
1667
|
+
* const host = new TestHost();
|
|
1668
|
+
* host.upscaling.reason = { code: "no-gpu", message: "no NVIDIA GPU" };
|
|
1669
|
+
* const { stop } = await startPlugin(activate, {
|
|
1670
|
+
* directory: new URL("..", import.meta.url),
|
|
1671
|
+
* host,
|
|
1672
|
+
* });
|
|
1673
|
+
* assert.equal(host.upscaling.checked, false); // starting woke no GPU
|
|
1674
|
+
* const item = host.command("upscaling");
|
|
1675
|
+
* await item.run(); // the first pick checks the GPU
|
|
1676
|
+
* assert.equal(item.state.enabled, false); // greyed out
|
|
1677
|
+
* assert.equal(item.state.reason, "no NVIDIA GPU"); // the player's message
|
|
1678
|
+
* await stop();
|
|
1679
|
+
* ```
|
|
1680
|
+
*
|
|
1681
|
+
* @since 1.5.37
|
|
1682
|
+
*/
|
|
1683
|
+
upscaling = {
|
|
1684
|
+
reason: null,
|
|
1685
|
+
enabled: false,
|
|
1686
|
+
factor: 2,
|
|
1687
|
+
checked: false,
|
|
1688
|
+
renderingStarting: false,
|
|
1689
|
+
};
|
|
1625
1690
|
/** What `player.chapters()` lists while media is ready. */
|
|
1626
1691
|
chapters = [];
|
|
1627
1692
|
#overrides = new Map();
|
|
@@ -3252,6 +3317,27 @@ export class TestHost {
|
|
|
3252
3317
|
revision: stamp.revision + 1,
|
|
3253
3318
|
};
|
|
3254
3319
|
}
|
|
3320
|
+
/** `video.upscaling.state` from {@link TestHost.upscaling}. */
|
|
3321
|
+
#upscalingState(check = true) {
|
|
3322
|
+
if (check)
|
|
3323
|
+
this.upscaling.checked = true;
|
|
3324
|
+
const { enabled, factor } = this.upscaling;
|
|
3325
|
+
// `set(false)` before any check: the player answers without checking.
|
|
3326
|
+
const reason = this.upscaling.checked
|
|
3327
|
+
? this.upscaling.reason
|
|
3328
|
+
: {
|
|
3329
|
+
code: "unchecked",
|
|
3330
|
+
message: "the player has not checked the GPU yet; state() or set(true) checks it",
|
|
3331
|
+
};
|
|
3332
|
+
const requested = enabled && reason === null && factor !== null && factor > 1;
|
|
3333
|
+
return {
|
|
3334
|
+
available: reason === null,
|
|
3335
|
+
reason: reason ? { ...reason } : null,
|
|
3336
|
+
enabled,
|
|
3337
|
+
requested,
|
|
3338
|
+
factor: requested ? factor : null,
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3255
3341
|
/** `rendering.state` from {@link TestHost.rendering} and `videoRender.gpu`. */
|
|
3256
3342
|
#renderingState() {
|
|
3257
3343
|
const { reason, preference, startedWith, gpu } = this.rendering;
|
|
@@ -3301,8 +3387,18 @@ export class TestHost {
|
|
|
3301
3387
|
};
|
|
3302
3388
|
}
|
|
3303
3389
|
#defaults = {
|
|
3304
|
-
"events.subscribe": (p) =>
|
|
3305
|
-
|
|
3390
|
+
"events.subscribe": (p) => {
|
|
3391
|
+
const event = String(p.event);
|
|
3392
|
+
this.subscriptions.add(event);
|
|
3393
|
+
if (p.mirror === true)
|
|
3394
|
+
this.listening.delete(event);
|
|
3395
|
+
else
|
|
3396
|
+
this.listening.add(event);
|
|
3397
|
+
},
|
|
3398
|
+
"events.unsubscribe": (p) => {
|
|
3399
|
+
this.subscriptions.delete(String(p.event));
|
|
3400
|
+
this.listening.delete(String(p.event));
|
|
3401
|
+
},
|
|
3306
3402
|
"commands.register": (p) => {
|
|
3307
3403
|
const id = String(p.id);
|
|
3308
3404
|
// The player's built-in menu ids (MENU_IDS) in the first path segment.
|
|
@@ -3640,6 +3736,26 @@ export class TestHost {
|
|
|
3640
3736
|
this.rendering.restarts++;
|
|
3641
3737
|
return null;
|
|
3642
3738
|
},
|
|
3739
|
+
// As the player 0.5.16 (see TestHost.upscaling for the model).
|
|
3740
|
+
"video.upscaling.state": () => this.#upscalingState(),
|
|
3741
|
+
"video.upscaling.set": (p) => {
|
|
3742
|
+
if (typeof p.enabled !== "boolean")
|
|
3743
|
+
throw new HostError("invalid", "enabled must be a boolean");
|
|
3744
|
+
const { reason, renderingStarting } = this.upscaling;
|
|
3745
|
+
// Off: always, without the GPU check.
|
|
3746
|
+
if (!p.enabled) {
|
|
3747
|
+
this.upscaling.enabled = false;
|
|
3748
|
+
return this.#upscalingState(false);
|
|
3749
|
+
}
|
|
3750
|
+
const passes = reason?.code === "decoding" ||
|
|
3751
|
+
(reason?.code === "rendering" && renderingStarting);
|
|
3752
|
+
if (reason && !passes) {
|
|
3753
|
+
this.upscaling.checked = true;
|
|
3754
|
+
throw new HostError("unsupported", `video upscaling cannot work here: ${reason.message}`);
|
|
3755
|
+
}
|
|
3756
|
+
this.upscaling.enabled = true;
|
|
3757
|
+
return this.#upscalingState();
|
|
3758
|
+
},
|
|
3643
3759
|
"clipboard.readText": () => this.clipboard.text,
|
|
3644
3760
|
"clipboard.writeText": (p) => {
|
|
3645
3761
|
if (typeof p.text !== "string")
|
|
@@ -16,7 +16,7 @@ export declare class TimelineService {
|
|
|
16
16
|
accept(marks: WireMark[]): void;
|
|
17
17
|
read(): Promise<void>;
|
|
18
18
|
watch(listener: (marks: readonly TimelineMark[]) => void): {
|
|
19
|
-
dispose: () => Promise<
|
|
19
|
+
dispose: () => Promise<void>;
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
export declare const timelineOf: (runtime: Runtime) => TimelineService;
|
package/dist/plugin/timeline.js
CHANGED
|
@@ -27,13 +27,14 @@ export class TimelineService {
|
|
|
27
27
|
if (this.#started)
|
|
28
28
|
return;
|
|
29
29
|
this.#started = true;
|
|
30
|
+
// A mirror: a plugin listens through `watch` and bindings.
|
|
30
31
|
this.#runtime.on("timeline.changed", (data) => {
|
|
31
32
|
const marks = data?.marks;
|
|
32
33
|
if (Array.isArray(marks))
|
|
33
34
|
this.accept(marks);
|
|
34
35
|
else
|
|
35
36
|
void this.read();
|
|
36
|
-
});
|
|
37
|
+
}, "events", true);
|
|
37
38
|
}
|
|
38
39
|
accept(marks) {
|
|
39
40
|
const next = view(marks);
|
|
@@ -51,7 +52,13 @@ export class TimelineService {
|
|
|
51
52
|
}
|
|
52
53
|
watch(listener) {
|
|
53
54
|
this.#listeners.add(listener);
|
|
54
|
-
|
|
55
|
+
const held = this.#runtime.hold("timeline.changed");
|
|
56
|
+
return {
|
|
57
|
+
dispose: async () => {
|
|
58
|
+
this.#listeners.delete(listener);
|
|
59
|
+
await held.dispose();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
export const timelineOf = (runtime) => runtime.service("timeline", () => new TimelineService(runtime));
|
|
@@ -60,6 +67,7 @@ export function timeline(scope) {
|
|
|
60
67
|
return {
|
|
61
68
|
get marks() {
|
|
62
69
|
service().start();
|
|
70
|
+
scope.runtime.reading("timeline.changed");
|
|
63
71
|
return service().marks;
|
|
64
72
|
},
|
|
65
73
|
onChange(listener) {
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -213,10 +213,11 @@ export interface Scope extends Disposable {
|
|
|
213
213
|
/** Playback state and controls (`playback.read`, `events`, `playback.control`). */
|
|
214
214
|
readonly player: Player;
|
|
215
215
|
/**
|
|
216
|
-
* Video processing: this plugin's filter chain, the display layer
|
|
217
|
-
* GPU render options.
|
|
218
|
-
* `playback.read`; the filter chain also needs
|
|
219
|
-
* SDK never sees the media become ready and the
|
|
216
|
+
* Video processing: this plugin's filter chain, the display layer, the
|
|
217
|
+
* GPU render options and the GPU's video upscaler. The first three need
|
|
218
|
+
* `processing.video` and `playback.read`; the filter chain also needs
|
|
219
|
+
* `events` (without it the SDK never sees the media become ready and the
|
|
220
|
+
* chain stays `waiting`). The upscaler needs `video.upscaling` only.
|
|
220
221
|
*/
|
|
221
222
|
readonly video: {
|
|
222
223
|
/**
|
|
@@ -249,6 +250,12 @@ export interface Scope extends Disposable {
|
|
|
249
250
|
* while the first handle is not disposed.
|
|
250
251
|
*/
|
|
251
252
|
render(): RenderOptions;
|
|
253
|
+
/**
|
|
254
|
+
* The GPU's video upscaler (NVIDIA RTX Video Super Resolution on
|
|
255
|
+
* Windows; needs `video.upscaling`, player 0.5.16): whether it works
|
|
256
|
+
* here and why not, and turning it on or off for this plugin.
|
|
257
|
+
*/
|
|
258
|
+
readonly upscaling: VideoUpscaling;
|
|
252
259
|
};
|
|
253
260
|
/**
|
|
254
261
|
* Audio processing: this plugin's filter chain and delay
|
|
@@ -6439,6 +6446,270 @@ export interface RenderOptions extends Disposable {
|
|
|
6439
6446
|
*/
|
|
6440
6447
|
onStatus(listener: (status: FilterStatus) => void): Disposable;
|
|
6441
6448
|
}
|
|
6449
|
+
/**
|
|
6450
|
+
* Why the GPU's video upscaler does not work, as a stable kebab-case code.
|
|
6451
|
+
* A newer player may add codes: compare with the ones you handle and show
|
|
6452
|
+
* {@link UpscalingReason.message} for the rest. The codes of player 0.5.16,
|
|
6453
|
+
* in the order the player checks them (the first that holds is reported):
|
|
6454
|
+
*
|
|
6455
|
+
* | Code | Meaning |
|
|
6456
|
+
* | --- | --- |
|
|
6457
|
+
* | `platform` | The player has no video upscaler on this system (macOS, Linux). |
|
|
6458
|
+
* | `no-gpu` | No GPU that can upscale video (Windows: no NVIDIA GPU). |
|
|
6459
|
+
* | `driver` | The graphics driver is too old (Windows: an NVIDIA driver before 530). |
|
|
6460
|
+
* | `gpu-unsupported` | The driver does not offer upscaling on this GPU (Windows: not an RTX GPU), or the check failed. |
|
|
6461
|
+
* | `rendering` | The player renders in software, or GPU rendering is still starting; upscaling needs GPU rendering ({@link Rendering}). |
|
|
6462
|
+
* | `adapter` | GPU rendering runs on another GPU than the one that can upscale (a laptop with two GPUs; Windows: choose the high-performance GPU for Vestra in Settings ▸ System ▸ Display ▸ Graphics). |
|
|
6463
|
+
* | `decoding` | The video is not decoded on the GPU that renders it: copy-back or software decoding, for example while a {@link VideoFilter} needs the frames in memory. |
|
|
6464
|
+
* | `unchecked` | The player has not checked the GPU yet. Only in the answer to `set(false)`, which never starts the check; `state()` and `set(true)` always check first. |
|
|
6465
|
+
*
|
|
6466
|
+
* `decoding` and `rendering` while GPU rendering is still starting can pass
|
|
6467
|
+
* during the run, so `set(true)` accepts them and keeps the request; every
|
|
6468
|
+
* other reason holds until the player restarts, and `set(true)` rejects it.
|
|
6469
|
+
* Turning upscaling off is always accepted.
|
|
6470
|
+
*
|
|
6471
|
+
* @example Advise the user by reason
|
|
6472
|
+
* The plugin shows its own advice for the reasons the user can act on and
|
|
6473
|
+
* the player's message for the rest. `reason.code` is looked up among the
|
|
6474
|
+
* codes the plugin knows; an unknown code from a newer player falls
|
|
6475
|
+
* through to the message, which names the GPU and what to do.
|
|
6476
|
+
* ```ts
|
|
6477
|
+
* const { reason } = await plugin.video.upscaling.state();
|
|
6478
|
+
* // Advice for the codes this plugin knows; any other code shows the message.
|
|
6479
|
+
* const advice: Partial<Record<UpscalingReasonCode, string>> = {
|
|
6480
|
+
* driver: "Update the NVIDIA driver to 530 or newer.",
|
|
6481
|
+
* rendering: "Turn on Video ▸ GPU Rendering (Experimental) and restart.",
|
|
6482
|
+
* };
|
|
6483
|
+
* if (reason) console.log(advice[reason.code] ?? reason.message);
|
|
6484
|
+
* ```
|
|
6485
|
+
*
|
|
6486
|
+
* @player 0.5.16
|
|
6487
|
+
* @since 1.5.37
|
|
6488
|
+
* @category Processing
|
|
6489
|
+
*/
|
|
6490
|
+
export type UpscalingReasonCode = "platform" | "no-gpu" | "driver" | "gpu-unsupported" | "rendering" | "adapter" | "decoding" | "unchecked" | (string & {});
|
|
6491
|
+
/**
|
|
6492
|
+
* Why the GPU's video upscaler does not work: a stable
|
|
6493
|
+
* {@link UpscalingReasonCode} to branch on and a sentence to show, the same
|
|
6494
|
+
* shape as {@link RenderingReason}.
|
|
6495
|
+
*
|
|
6496
|
+
* @example Grey out a menu item with the player's reason
|
|
6497
|
+
* The plugin disables its upscaling item and shows why. `message` is the
|
|
6498
|
+
* player's English text, naming the GPU and what the user can do (turn on
|
|
6499
|
+
* GPU rendering, choose the other GPU, update the driver); `code` is for
|
|
6500
|
+
* decisions.
|
|
6501
|
+
* ```ts
|
|
6502
|
+
* const { available, reason } = await plugin.video.upscaling.state();
|
|
6503
|
+
* if (!available && reason) console.log(`${reason.code}: ${reason.message}`);
|
|
6504
|
+
* ```
|
|
6505
|
+
*
|
|
6506
|
+
* @player 0.5.16
|
|
6507
|
+
* @since 1.5.37
|
|
6508
|
+
* @category Processing
|
|
6509
|
+
*/
|
|
6510
|
+
export interface UpscalingReason {
|
|
6511
|
+
/** The reason as a stable kebab-case code; branch on this. */
|
|
6512
|
+
readonly code: UpscalingReasonCode;
|
|
6513
|
+
/**
|
|
6514
|
+
* The reason as English text for people, for example with the GPU's name
|
|
6515
|
+
* and what to change; never branch on it.
|
|
6516
|
+
*/
|
|
6517
|
+
readonly message: string;
|
|
6518
|
+
}
|
|
6519
|
+
/**
|
|
6520
|
+
* What {@link VideoUpscaling.state} and {@link VideoUpscaling.set} report:
|
|
6521
|
+
* whether the upscaler works now and why not, whether this plugin turned
|
|
6522
|
+
* it on, and whether the player asked the GPU to upscale.
|
|
6523
|
+
*
|
|
6524
|
+
* @remarks
|
|
6525
|
+
* `requested` is as far as the player can see. The player puts the
|
|
6526
|
+
* upscaler last in the video filter chain, scaling the video to the area it
|
|
6527
|
+
* is shown in; the driver gives no sign that it upscales. On Windows the
|
|
6528
|
+
* user must also turn on Super Resolution in the NVIDIA app (System ▸ Video
|
|
6529
|
+
* ▸ RTX Video Enhancement), which no program can read: say so next to the
|
|
6530
|
+
* switch.
|
|
6531
|
+
*
|
|
6532
|
+
* @example Label the item with what happens now
|
|
6533
|
+
* The plugin's menu item says whether upscaling is on and whether the
|
|
6534
|
+
* video is being upscaled. `enabled` is this plugin's own request;
|
|
6535
|
+
* `requested` is true while the player has the upscaler in the filter
|
|
6536
|
+
* chain, which needs a video shown larger than its own size (`factor`
|
|
6537
|
+
* above 1).
|
|
6538
|
+
* ```ts
|
|
6539
|
+
* const state = await plugin.video.upscaling.state();
|
|
6540
|
+
* // `enabled`: this plugin's request; `requested`: in the filter chain now.
|
|
6541
|
+
* const label = !state.enabled
|
|
6542
|
+
* ? "AI Upscaling: Off"
|
|
6543
|
+
* : state.requested
|
|
6544
|
+
* ? `AI Upscaling: On (×${state.factor?.toFixed(2)})`
|
|
6545
|
+
* : "AI Upscaling: On (not needed for this video)";
|
|
6546
|
+
* console.log(label);
|
|
6547
|
+
* ```
|
|
6548
|
+
*
|
|
6549
|
+
* @player 0.5.16
|
|
6550
|
+
* @since 1.5.37
|
|
6551
|
+
* @category Processing
|
|
6552
|
+
*/
|
|
6553
|
+
export interface UpscalingState {
|
|
6554
|
+
/** Whether the upscaler works now; `false` with {@link UpscalingState.reason}. */
|
|
6555
|
+
readonly available: boolean;
|
|
6556
|
+
/** Why it does not work now; `null` while it does. */
|
|
6557
|
+
readonly reason: UpscalingReason | null;
|
|
6558
|
+
/** This plugin asked for upscaling ({@link VideoUpscaling.set}) and has not turned it off. */
|
|
6559
|
+
readonly enabled: boolean;
|
|
6560
|
+
/**
|
|
6561
|
+
* The player has the upscaler in the video filter chain now, for this
|
|
6562
|
+
* plugin or another one: upscaling is enabled and available and the
|
|
6563
|
+
* video is shown larger than its own size. Not a sign that the GPU
|
|
6564
|
+
* upscales (see the remarks).
|
|
6565
|
+
*/
|
|
6566
|
+
readonly requested: boolean;
|
|
6567
|
+
/**
|
|
6568
|
+
* The scale factor the upscaler was asked for (the area the video is
|
|
6569
|
+
* shown in over the video's size, in hundredths, above 1); `null` while
|
|
6570
|
+
* nothing is requested.
|
|
6571
|
+
*/
|
|
6572
|
+
readonly factor: number | null;
|
|
6573
|
+
}
|
|
6574
|
+
/**
|
|
6575
|
+
* `plugin.video.upscaling`: the GPU's video upscaler, NVIDIA RTX Video
|
|
6576
|
+
* Super Resolution on Windows (`video.upscaling`). Read whether it works
|
|
6577
|
+
* here and why not; turn it on or off for this plugin.
|
|
6578
|
+
*
|
|
6579
|
+
* @remarks
|
|
6580
|
+
* The player checks the GPU once, when a plugin first asks, and keeps the
|
|
6581
|
+
* answer: the check wakes a sleeping discrete GPU, so call
|
|
6582
|
+
* {@link VideoUpscaling.state} when the plugin first needs the answer (the
|
|
6583
|
+
* first time the user picks its upscaling item), not while the plugin
|
|
6584
|
+
* starts, and do not poll it. `set(false)` never starts the check. It works
|
|
6585
|
+
* only with GPU rendering, on the GPU the upscaler runs on, with the video decoded on that GPU; {@link UpscalingReasonCode}
|
|
6586
|
+
* lists what can stand in the way.
|
|
6587
|
+
*
|
|
6588
|
+
* While some plugin has it on and it works, the player puts the upscaler
|
|
6589
|
+
* last in the video filter chain and scales the video to the area it is
|
|
6590
|
+
* shown in, following window and video size changes (a resize waits until
|
|
6591
|
+
* the size holds). A video already shown at its own size or smaller is not
|
|
6592
|
+
* upscaled. The upscaler stays while any plugin has it on; it goes when the
|
|
6593
|
+
* last one turns it off or stops: the player revokes a stopped plugin's
|
|
6594
|
+
* request.
|
|
6595
|
+
*
|
|
6596
|
+
* **Windows:** the user must turn on Super Resolution in the NVIDIA app
|
|
6597
|
+
* (System ▸ Video ▸ RTX Video Enhancement); no program can read that
|
|
6598
|
+
* switch, and the driver gives no sign that it upscales
|
|
6599
|
+
* ({@link UpscalingState.requested} is as far as the player can see). It
|
|
6600
|
+
* uses the NVIDIA GPU, so power use and heat go up, and on a laptop with
|
|
6601
|
+
* two GPUs the whole player must run on the NVIDIA one.
|
|
6602
|
+
* **macOS, Linux:** `available` is `false` with `platform`.
|
|
6603
|
+
*
|
|
6604
|
+
* Players before 0.5.16 do not know `video.upscaling` and do not load a
|
|
6605
|
+
* plugin that declares it: declare `"minPlayerVersion": "0.5.16"`.
|
|
6606
|
+
*
|
|
6607
|
+
* @example Offer AI upscaling as a menu item
|
|
6608
|
+
* A Video menu item turns upscaling on and off; once the player said it
|
|
6609
|
+
* cannot work here, the item is greyed out with the player's reason. The
|
|
6610
|
+
* state is read the first time the user picks the item (that runs the
|
|
6611
|
+
* player's GPU check, so nothing wakes the GPU while the plugin starts) and
|
|
6612
|
+
* again after each pick; `set` resolves with the new state, or rejects with
|
|
6613
|
+
* `unsupported` where upscaling cannot work in this run.
|
|
6614
|
+
* ```ts
|
|
6615
|
+
* let state: UpscalingState | null = null; // unknown until first picked
|
|
6616
|
+
* plugin.commands.add({
|
|
6617
|
+
* id: "upscaling",
|
|
6618
|
+
* path: ["@video", "AI Upscaling"],
|
|
6619
|
+
* checked: () => state?.enabled ?? false,
|
|
6620
|
+
* // Greyed only once the player said it cannot work (`decoding` can pass).
|
|
6621
|
+
* enabled: () => state === null || state.available || state.reason?.code === "decoding",
|
|
6622
|
+
* reason: () => state?.reason?.message ?? null, // shown while disabled
|
|
6623
|
+
* run: async () => {
|
|
6624
|
+
* state ??= await plugin.video.upscaling.state(); // the first pick checks the GPU
|
|
6625
|
+
* try {
|
|
6626
|
+
* state = await plugin.video.upscaling.set(!(state?.enabled ?? false));
|
|
6627
|
+
* } catch (error) {
|
|
6628
|
+
* // Cannot work in this run (no RTX GPU, software rendering…).
|
|
6629
|
+
* if ((error as PluginError).code !== "unsupported") throw error;
|
|
6630
|
+
* state = await plugin.video.upscaling.state();
|
|
6631
|
+
* }
|
|
6632
|
+
* },
|
|
6633
|
+
* });
|
|
6634
|
+
* ```
|
|
6635
|
+
*
|
|
6636
|
+
* @capability video.upscaling
|
|
6637
|
+
* @player 0.5.16; older players do not load a plugin that declares
|
|
6638
|
+
* `video.upscaling`.
|
|
6639
|
+
* @since 1.5.37
|
|
6640
|
+
* @category Processing
|
|
6641
|
+
*/
|
|
6642
|
+
export interface VideoUpscaling {
|
|
6643
|
+
/**
|
|
6644
|
+
* Read whether the upscaler works now and why not, whether this plugin
|
|
6645
|
+
* turned it on and what the player asked the GPU for.
|
|
6646
|
+
*
|
|
6647
|
+
* @capability video.upscaling
|
|
6648
|
+
* @returns The state, once the player's GPU check ran (the first call
|
|
6649
|
+
* starts it and may take a moment).
|
|
6650
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6651
|
+
* `video.upscaling`.
|
|
6652
|
+
*
|
|
6653
|
+
* @example Find out whether upscaling can work here
|
|
6654
|
+
* The plugin reads the state the first time the user picks its upscaling
|
|
6655
|
+
* item and keeps it for the item. The first call makes the player check the GPU (which
|
|
6656
|
+
* wakes a sleeping discrete GPU) and later calls reuse the answer; the
|
|
6657
|
+
* rest (GPU rendering, the GPU it runs on, the decoding) is read at
|
|
6658
|
+
* each call.
|
|
6659
|
+
* ```ts
|
|
6660
|
+
* const { available, reason } = await plugin.video.upscaling.state();
|
|
6661
|
+
* if (!available) console.log(reason?.message);
|
|
6662
|
+
* ```
|
|
6663
|
+
*
|
|
6664
|
+
* @since 1.5.37
|
|
6665
|
+
*/
|
|
6666
|
+
state(): Promise<UpscalingState>;
|
|
6667
|
+
/**
|
|
6668
|
+
* Turn upscaling on or off for this plugin.
|
|
6669
|
+
*
|
|
6670
|
+
* @remarks
|
|
6671
|
+
* On stays this plugin's request until it turns it off or stops; the
|
|
6672
|
+
* player upscales whenever the request can take effect (a `decoding`
|
|
6673
|
+
* reason can pass with the next file). Off takes the upscaler out at
|
|
6674
|
+
* once unless another plugin has it on. Upscaling is a request of the
|
|
6675
|
+
* whole plugin, not of a {@link Scope}: disposing a scope does not turn
|
|
6676
|
+
* it off.
|
|
6677
|
+
*
|
|
6678
|
+
* @capability video.upscaling
|
|
6679
|
+
* @param enabled - `true` to ask for upscaling, `false` to withdraw the
|
|
6680
|
+
* request.
|
|
6681
|
+
* @returns The new state.
|
|
6682
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6683
|
+
* `video.upscaling`.
|
|
6684
|
+
* @throws `invalid` (rejects) `enabled` is not a boolean.
|
|
6685
|
+
* @throws `unsupported` (rejects) `true` where upscaling cannot work in
|
|
6686
|
+
* this run: every {@link UpscalingReasonCode} except `decoding`, and
|
|
6687
|
+
* except `rendering` while GPU rendering is still starting (those two
|
|
6688
|
+
* are accepted and the request is kept until they pass); `message` is
|
|
6689
|
+
* the reason. Nothing changes. `false` is always accepted and answered
|
|
6690
|
+
* at once without the GPU check (before any check, `available` is
|
|
6691
|
+
* `false` with `unchecked`).
|
|
6692
|
+
*
|
|
6693
|
+
* @example Turn upscaling on and explain what is needed
|
|
6694
|
+
* The plugin turns upscaling on from its settings and tells the user what
|
|
6695
|
+
* else to switch on. `set(true)` records the request and resolves with the
|
|
6696
|
+
* new state; where upscaling cannot work in this run it rejects with
|
|
6697
|
+
* `unsupported` and the player's reason instead.
|
|
6698
|
+
* ```ts
|
|
6699
|
+
* try {
|
|
6700
|
+
* const state = await plugin.video.upscaling.set(true);
|
|
6701
|
+
* // The driver's own switch cannot be read: remind the user once.
|
|
6702
|
+
* if (state.enabled) await plugin.osd.show("Turn on Super Resolution in the NVIDIA app");
|
|
6703
|
+
* } catch (error) {
|
|
6704
|
+
* if ((error as PluginError).code !== "unsupported") throw error;
|
|
6705
|
+
* await plugin.osd.show("AI upscaling: {}", [(error as PluginError).message]);
|
|
6706
|
+
* }
|
|
6707
|
+
* ```
|
|
6708
|
+
*
|
|
6709
|
+
* @since 1.5.37
|
|
6710
|
+
*/
|
|
6711
|
+
set(enabled: boolean): Promise<UpscalingState>;
|
|
6712
|
+
}
|
|
6442
6713
|
/**
|
|
6443
6714
|
* A subtitle track of the current source.
|
|
6444
6715
|
*
|
|
@@ -7700,6 +7971,17 @@ export interface PluginInfo {
|
|
|
7700
7971
|
* @since 1.5.9
|
|
7701
7972
|
*/
|
|
7702
7973
|
display?: PluginDisplay;
|
|
7974
|
+
/**
|
|
7975
|
+
* For `state: "running"`: `"running"` while its process runs, `"idle"`
|
|
7976
|
+
* while the player stopped it because nothing used it (lazy activation:
|
|
7977
|
+
* it starts again on its next use, its menu items, keys, settings pages,
|
|
7978
|
+
* resolvers and drop targets stay). Absent in other states and from
|
|
7979
|
+
* players before 0.5.18. A change between the two alone does not fire
|
|
7980
|
+
* {@link Packages.onChange}.
|
|
7981
|
+
*
|
|
7982
|
+
* @since 1.5.38
|
|
7983
|
+
*/
|
|
7984
|
+
process?: "running" | "idle";
|
|
7703
7985
|
}
|
|
7704
7986
|
/**
|
|
7705
7987
|
* {@link PluginInfo.display}: a package's `name`, `punchline`,
|
package/dist/plugin/ui.js
CHANGED
|
@@ -534,13 +534,20 @@ class LayoutService {
|
|
|
534
534
|
if (this.#started || !this.#runtime.can("ui.mount"))
|
|
535
535
|
return;
|
|
536
536
|
this.#started = true;
|
|
537
|
-
|
|
537
|
+
// A mirror: panels listen through `watch`.
|
|
538
|
+
this.#runtime.on("window.state", () => void this.read(), "events", true);
|
|
538
539
|
void this.read();
|
|
539
540
|
}
|
|
540
541
|
watch(listener) {
|
|
541
542
|
this.start();
|
|
542
543
|
this.#listeners.add(listener);
|
|
543
|
-
|
|
544
|
+
const held = this.#runtime.hold("window.state");
|
|
545
|
+
return {
|
|
546
|
+
dispose: async () => {
|
|
547
|
+
this.#listeners.delete(listener);
|
|
548
|
+
await held.dispose();
|
|
549
|
+
},
|
|
550
|
+
};
|
|
544
551
|
}
|
|
545
552
|
async read() {
|
|
546
553
|
if (this.#reading) {
|
package/dist/plugin/window.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class WindowService {
|
|
|
26
26
|
accept(facts: WindowFacts): void;
|
|
27
27
|
read(): Promise<void>;
|
|
28
28
|
watch(listener: (state: WindowState) => void): {
|
|
29
|
-
dispose: () => Promise<
|
|
29
|
+
dispose: () => Promise<void>;
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export declare const windowOf: (runtime: Runtime) => WindowService;
|
package/dist/plugin/window.js
CHANGED
|
@@ -40,6 +40,7 @@ export class WindowService {
|
|
|
40
40
|
if (this.#started)
|
|
41
41
|
return;
|
|
42
42
|
this.#started = true;
|
|
43
|
+
// A mirror: a plugin listens through `watch` and bindings.
|
|
43
44
|
this.#runtime.on("window.state", (data) => {
|
|
44
45
|
const facts = data;
|
|
45
46
|
// The event carries the facts; anything else is read again.
|
|
@@ -47,7 +48,7 @@ export class WindowService {
|
|
|
47
48
|
this.accept(facts);
|
|
48
49
|
else
|
|
49
50
|
void this.read();
|
|
50
|
-
});
|
|
51
|
+
}, "events", true);
|
|
51
52
|
void this.read();
|
|
52
53
|
}
|
|
53
54
|
/** The palette, as the `window.state` reply carried it (`plugin.appearance`). */
|
|
@@ -91,7 +92,13 @@ export class WindowService {
|
|
|
91
92
|
}
|
|
92
93
|
watch(listener) {
|
|
93
94
|
this.#listeners.add(listener);
|
|
94
|
-
|
|
95
|
+
const held = this.#runtime.hold("window.state");
|
|
96
|
+
return {
|
|
97
|
+
dispose: async () => {
|
|
98
|
+
this.#listeners.delete(listener);
|
|
99
|
+
await held.dispose();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
export const windowOf = (runtime) => runtime.service("window", () => new WindowService(runtime));
|
|
@@ -101,6 +108,7 @@ export function mainWindow(scope) {
|
|
|
101
108
|
return {
|
|
102
109
|
get state() {
|
|
103
110
|
service().start();
|
|
111
|
+
scope.runtime.reading("window.state");
|
|
104
112
|
return service().state;
|
|
105
113
|
},
|
|
106
114
|
onChange(listener) {
|
package/dist/wire.d.ts
CHANGED
|
@@ -141,6 +141,8 @@ export interface PackageInfo {
|
|
|
141
141
|
description: string | null;
|
|
142
142
|
tags: string[];
|
|
143
143
|
};
|
|
144
|
+
/** Whether it has a process now (player 0.5.18, `state: "running"`). */
|
|
145
|
+
process?: "running" | "idle";
|
|
144
146
|
}
|
|
145
147
|
/** Where an installed package came from (`packages.install` `source`). */
|
|
146
148
|
export type WireSource = {
|
|
@@ -336,6 +338,15 @@ export interface WireRenderingState {
|
|
|
336
338
|
reason: WireRenderingReason | null;
|
|
337
339
|
};
|
|
338
340
|
}
|
|
341
|
+
/** `video.upscaling.state` and `video.upscaling.set` (player 0.5.16). */
|
|
342
|
+
export interface WireUpscalingState {
|
|
343
|
+
available: boolean;
|
|
344
|
+
/** The same `{ code, message }` shape as a rendering reason. */
|
|
345
|
+
reason: WireRenderingReason | null;
|
|
346
|
+
enabled: boolean;
|
|
347
|
+
requested: boolean;
|
|
348
|
+
factor: number | null;
|
|
349
|
+
}
|
|
339
350
|
/**
|
|
340
351
|
* Template arguments on the wire (`osd.show`, `osd.panel`, `dialogs.confirm`,
|
|
341
352
|
* a text's `args`): data, or text values (player 0.4.3).
|
|
@@ -356,6 +367,8 @@ export interface Transport {
|
|
|
356
367
|
*/
|
|
357
368
|
locale?: string;
|
|
358
369
|
}
|
|
370
|
+
/** The events a plugin subscribes to (`events.subscribe`). */
|
|
371
|
+
type HostEvent = "source.state" | "playback.backend" | "window.state" | "packages.changed" | "bindings.changed" | "timeline.changed" | "appearance.changed" | "resolvers.fileTypes";
|
|
359
372
|
export declare function createWire(transport: Transport): {
|
|
360
373
|
processing: {
|
|
361
374
|
state: () => Promise<import("./processing.ts").ProcessingState>;
|
|
@@ -468,6 +481,9 @@ export declare function createWire(transport: Transport): {
|
|
|
468
481
|
state: "applied" | "waiting";
|
|
469
482
|
}>;
|
|
470
483
|
renderRelease: () => Promise<void>;
|
|
484
|
+
/** The GPU's video upscaler (`plugin.video.upscaling`, player 0.5.16). */
|
|
485
|
+
upscalingState: () => Promise<WireUpscalingState>;
|
|
486
|
+
upscalingSet: (enabled: boolean) => Promise<WireUpscalingState>;
|
|
471
487
|
};
|
|
472
488
|
/** Mark layers on the seek bar (`plugin.timeline`). */
|
|
473
489
|
timeline: {
|
|
@@ -587,8 +603,19 @@ export declare function createWire(transport: Transport): {
|
|
|
587
603
|
}) => Promise<void>;
|
|
588
604
|
};
|
|
589
605
|
events: {
|
|
590
|
-
/**
|
|
591
|
-
|
|
606
|
+
/**
|
|
607
|
+
* One `events.subscribe` per name however many listeners. `mirror`:
|
|
608
|
+
* only the SDK keeps a copy of the state (player 0.5.18; older
|
|
609
|
+
* players ignore it); `listen` changes it.
|
|
610
|
+
*/
|
|
611
|
+
subscribe(event: HostEvent, listener: (data: unknown) => void, mirror?: boolean): Promise<() => Promise<void>>;
|
|
612
|
+
/**
|
|
613
|
+
* Tell the player whether a subscribed event has a real listener
|
|
614
|
+
* (`active`) or only the SDK's copy of the state: `events.subscribe`
|
|
615
|
+
* again, which replaces the mark (player 0.5.18; older players keep
|
|
616
|
+
* one subscription per name and ignore the mark).
|
|
617
|
+
*/
|
|
618
|
+
listen: (event: HostEvent, active: boolean) => Promise<void>;
|
|
592
619
|
};
|
|
593
620
|
ui: {
|
|
594
621
|
/** Player 0.2.0: the remote images a Markdown document shows. */
|