@vestra-player/sdk 1.5.31 → 1.5.33
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 +15 -0
- package/dist/capabilities.d.ts +6 -0
- package/dist/capabilities.js +4 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.js +14 -0
- package/dist/i18n.d.ts +9 -0
- package/dist/i18n.js +9 -0
- package/dist/palettes.d.ts +2 -0
- package/dist/palettes.js +2 -0
- package/dist/plugin/bindings.d.ts +2 -0
- package/dist/plugin/bindings.js +2 -0
- package/dist/plugin/osd.d.ts +2 -0
- package/dist/plugin/osd.js +2 -0
- package/dist/plugin/services.d.ts +4 -0
- package/dist/plugin/services.js +4 -0
- package/dist/plugin/testing.d.ts +518 -9
- package/dist/plugin/testing.js +384 -16
- package/dist/plugin/types.d.ts +1470 -59
- package/dist/plugin/values.d.ts +5 -0
- package/dist/plugin/values.js +5 -0
- package/dist/processing.d.ts +6 -0
- package/dist/source-resolvers.d.ts +3 -0
- package/dist/subtitles.d.ts +3 -0
- package/dist/wire.d.ts +13 -0
- package/dist/wire.js +10 -0
- package/llms-full.txt +1362 -46
- package/llms.txt +22 -22
- package/package.json +1 -1
package/dist/plugin/types.d.ts
CHANGED
|
@@ -43,11 +43,39 @@ export type { Json, AudioNode, VideoNode, SubtitleStyle, ResolvedMedia };
|
|
|
43
43
|
* if (state.phase === "ready") void subscription.dispose();
|
|
44
44
|
* });
|
|
45
45
|
* ```
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* Every handle a plugin creates is a `Disposable` owned by the scope that
|
|
49
|
+
* created it; disposing the scope (or stopping the plugin) disposes them in
|
|
50
|
+
* reverse creation order, so a plugin rarely calls `dispose` itself.
|
|
51
|
+
*
|
|
52
|
+
* @capability None of its own: that of the handle.
|
|
53
|
+
* @since 1.0.0
|
|
54
|
+
* @category Plugin
|
|
46
55
|
*/
|
|
47
56
|
export interface Disposable {
|
|
48
57
|
/**
|
|
49
58
|
* Release the resource. Resolves once the player confirmed the release
|
|
50
59
|
* (or immediately for purely local resources). Never rejects.
|
|
60
|
+
*
|
|
61
|
+
* @capability None of its own: that of the handle.
|
|
62
|
+
* @throws Never.
|
|
63
|
+
*
|
|
64
|
+
* @example Remove a command when its feature is turned off
|
|
65
|
+
* A "Turn feature off" item removes the feature's own menu item at once,
|
|
66
|
+
* before the plugin stops. `dispose` takes the command out of the menu and
|
|
67
|
+
* the binding table and resolves once the player confirmed it; the id can
|
|
68
|
+
* be added again later.
|
|
69
|
+
* ```ts
|
|
70
|
+
* const extra = plugin.commands.add({ id: "extra", path: ["Tools", "Extra"], run() {} });
|
|
71
|
+
* plugin.commands.add({
|
|
72
|
+
* id: "extra-off",
|
|
73
|
+
* path: ["Tools", "Turn extra off"],
|
|
74
|
+
* run: () => extra.dispose(), // never rejects
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @since 1.0.0
|
|
51
79
|
*/
|
|
52
80
|
dispose(): Promise<void>;
|
|
53
81
|
}
|
|
@@ -59,7 +87,6 @@ export interface Disposable {
|
|
|
59
87
|
* | code | meaning |
|
|
60
88
|
* | --- | --- |
|
|
61
89
|
* | `stale` | A condition no longer held (the source or the subtitle selection changed); nothing was done. See {@link Conditional}. |
|
|
62
|
-
* | `staleDigest` | {@link PackageCandidate.install}: the package's files changed after it was validated; validate it again. |
|
|
63
90
|
* | `capability` | The manifest does not declare the capability the call needs. |
|
|
64
91
|
* | `invalid` | A bad argument (out of range, malformed, unknown id). |
|
|
65
92
|
* | `missing` | The target does not exist (an empty panel position, a missing file, an unknown command). |
|
|
@@ -79,7 +106,7 @@ export interface Disposable {
|
|
|
79
106
|
* | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
80
107
|
* | `limit` | A player-wide limit was reached (for example the number of commands), a property value is over the limits of {@link PropValue}, or a request is longer than the player's 1 MiB message. |
|
|
81
108
|
* | `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). |
|
|
82
|
-
* | `versionMismatch` | The package needs a newer player (`minPlayerVersion`)
|
|
109
|
+
* | `versionMismatch` | The package needs a newer player (`minPlayerVersion`). |
|
|
83
110
|
* | `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. |
|
|
84
111
|
* | `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. Also reported to {@link Scope.onError} without a request: a callback whose arguments the player could not convert, a property it could not show again when it rebuilt a view (an image file removed since). |
|
|
85
112
|
* | `host` | Any other failure inside the player; `message` is the player's reason. |
|
|
@@ -117,6 +144,9 @@ export interface Disposable {
|
|
|
117
144
|
* throw error;
|
|
118
145
|
* }
|
|
119
146
|
* ```
|
|
147
|
+
*
|
|
148
|
+
* @since 1.0.0
|
|
149
|
+
* @category Plugin
|
|
120
150
|
*/
|
|
121
151
|
export type PluginError = Error & {
|
|
122
152
|
/** The stable failure code (see the table above). */
|
|
@@ -159,6 +189,17 @@ export type PluginError = Error & {
|
|
|
159
189
|
* );
|
|
160
190
|
* });
|
|
161
191
|
* ```
|
|
192
|
+
*
|
|
193
|
+
* @remarks
|
|
194
|
+
* Every module of the API (`commands`, `windows`, `player`…) is reachable
|
|
195
|
+
* from a scope, and every handle made through it belongs to it. A child
|
|
196
|
+
* scope from {@link Scope.scope} is the unit for features that come and
|
|
197
|
+
* go: dispose the child and everything it created is released, while the
|
|
198
|
+
* rest of the plugin keeps running.
|
|
199
|
+
*
|
|
200
|
+
* @capability That of each module (see the modules).
|
|
201
|
+
* @since 1.0.0
|
|
202
|
+
* @category Plugin
|
|
162
203
|
*/
|
|
163
204
|
export interface Scope extends Disposable {
|
|
164
205
|
/** Menu commands and their default bindings (`commands`). */
|
|
@@ -189,12 +230,23 @@ export interface Scope extends Disposable {
|
|
|
189
230
|
/**
|
|
190
231
|
* The display layer (needs `processing.video`). The first plugin to call
|
|
191
232
|
* it owns the layer; another plugin's view fails with `conflict` until the
|
|
192
|
-
* owner disposes or stops.
|
|
233
|
+
* owner disposes or stops.
|
|
234
|
+
*
|
|
235
|
+
* @throws `capability` (throws) The manifest does not declare
|
|
236
|
+
* `processing.video`.
|
|
237
|
+
* @throws `Error` without a code (throws) A second call in the same plugin
|
|
238
|
+
* while the first view is not disposed.
|
|
193
239
|
*/
|
|
194
240
|
view(): VideoView;
|
|
195
241
|
/**
|
|
196
242
|
* The GPU renderer's options (needs `processing.video`), owned like the
|
|
197
|
-
* display layer. `failed` with
|
|
243
|
+
* display layer. Under software rendering the handle is `failed` with
|
|
244
|
+
* `unsupported` after the player's first answer, and `onError` hears it.
|
|
245
|
+
*
|
|
246
|
+
* @throws `capability` (throws) The manifest does not declare
|
|
247
|
+
* `processing.video`.
|
|
248
|
+
* @throws `Error` without a code (throws) A second call in the same plugin
|
|
249
|
+
* while the first handle is not disposed.
|
|
198
250
|
*/
|
|
199
251
|
render(): RenderOptions;
|
|
200
252
|
};
|
|
@@ -258,6 +310,36 @@ export interface Scope extends Disposable {
|
|
|
258
310
|
/**
|
|
259
311
|
* A child scope; disposing it releases only what was created through it.
|
|
260
312
|
* The child is itself owned by this scope.
|
|
313
|
+
*
|
|
314
|
+
* @capability None.
|
|
315
|
+
*
|
|
316
|
+
* @example Tie a temporary window and its timer to one feature
|
|
317
|
+
* A "Live stats" feature opens a window and starts a timer; turning it off
|
|
318
|
+
* releases both together. Everything made through the child scope, and
|
|
319
|
+
* cleanups registered on it, go when the child is disposed; the parent
|
|
320
|
+
* scope is untouched.
|
|
321
|
+
* ```ts
|
|
322
|
+
* let live: Scope | null = null;
|
|
323
|
+
* plugin.commands.add({
|
|
324
|
+
* id: "live-stats",
|
|
325
|
+
* path: ["Tools", "Live stats"],
|
|
326
|
+
* checked: () => live !== null,
|
|
327
|
+
* run: async () => {
|
|
328
|
+
* if (live) {
|
|
329
|
+
* await live.dispose(); // closes the window, stops the timer
|
|
330
|
+
* live = null;
|
|
331
|
+
* return;
|
|
332
|
+
* }
|
|
333
|
+
* live = plugin.scope();
|
|
334
|
+
* const window = live.windows.create("ui/stats.slint");
|
|
335
|
+
* const timer = setInterval(() => live?.update(), 1000);
|
|
336
|
+
* live.onDispose(() => clearInterval(timer));
|
|
337
|
+
* await window.show();
|
|
338
|
+
* },
|
|
339
|
+
* });
|
|
340
|
+
* ```
|
|
341
|
+
*
|
|
342
|
+
* @since 1.0.0
|
|
261
343
|
*/
|
|
262
344
|
scope(): Scope;
|
|
263
345
|
/**
|
|
@@ -266,6 +348,21 @@ export interface Scope extends Disposable {
|
|
|
266
348
|
* @param item - Any {@link Disposable}, for example a handle created
|
|
267
349
|
* through another scope.
|
|
268
350
|
* @returns `item` itself, for chaining.
|
|
351
|
+
*
|
|
352
|
+
* @capability None.
|
|
353
|
+
*
|
|
354
|
+
* @example Dispose a handle from another scope together with this one
|
|
355
|
+
* A filter made through the root scope is withdrawn when a child feature
|
|
356
|
+
* ends. `add` hands an existing disposable to this scope, which then
|
|
357
|
+
* disposes it with its own handles; it returns the same handle for
|
|
358
|
+
* chaining.
|
|
359
|
+
* ```ts
|
|
360
|
+
* const feature = plugin.scope();
|
|
361
|
+
* const blur = feature.add(plugin.video.filter("blur")); // now ends with `feature`
|
|
362
|
+
* blur.set([{ kind: "gblur", sigma: 4, steps: 3 }]);
|
|
363
|
+
* ```
|
|
364
|
+
*
|
|
365
|
+
* @since 1.0.0
|
|
269
366
|
*/
|
|
270
367
|
add<T extends Disposable>(item: T): T;
|
|
271
368
|
/**
|
|
@@ -283,6 +380,9 @@ export interface Scope extends Disposable {
|
|
|
283
380
|
* const timer = setInterval(() => plugin.update(), 1000); // re-evaluate bindings
|
|
284
381
|
* plugin.onDispose(() => clearInterval(timer));
|
|
285
382
|
* ```
|
|
383
|
+
*
|
|
384
|
+
* @capability None.
|
|
385
|
+
* @since 1.0.0
|
|
286
386
|
*/
|
|
287
387
|
onDispose(cleanup: () => void | Promise<void>): Disposable;
|
|
288
388
|
/**
|
|
@@ -304,6 +404,9 @@ export interface Scope extends Disposable {
|
|
|
304
404
|
* void plugin.osd.show("Bookmarks: {}", [error.message]);
|
|
305
405
|
* });
|
|
306
406
|
* ```
|
|
407
|
+
*
|
|
408
|
+
* @capability None.
|
|
409
|
+
* @since 1.0.0
|
|
307
410
|
*/
|
|
308
411
|
onError(listener: (error: PluginError) => void): Disposable;
|
|
309
412
|
/**
|
|
@@ -313,6 +416,30 @@ export interface Scope extends Disposable {
|
|
|
313
416
|
* changing state from your own timers or between awaits of a long task.
|
|
314
417
|
*
|
|
315
418
|
* Several calls within one microtask are merged into one pass.
|
|
419
|
+
*
|
|
420
|
+
* @capability None.
|
|
421
|
+
*
|
|
422
|
+
* @example Show progress from a long task between awaits
|
|
423
|
+
* A progress label moves while a long download runs, not only when it
|
|
424
|
+
* ends. The SDK re-evaluates bound values after each handler and when its
|
|
425
|
+
* promise settles; state changed between the awaits of a long handler
|
|
426
|
+
* shows only after an explicit `update()`.
|
|
427
|
+
* ```ts
|
|
428
|
+
* declare const window: Window;
|
|
429
|
+
* declare function downloadPart(part: number): Promise<void>;
|
|
430
|
+
* // ---cut---
|
|
431
|
+
* let done = 0;
|
|
432
|
+
* window.bind(() => ({ progress: done }));
|
|
433
|
+
* window.on("start", async () => {
|
|
434
|
+
* for (let part = 0; part < 10; part++) {
|
|
435
|
+
* await downloadPart(part);
|
|
436
|
+
* done = part + 1;
|
|
437
|
+
* plugin.update(); // send `progress` now, not when the handler ends
|
|
438
|
+
* }
|
|
439
|
+
* });
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @since 1.0.0
|
|
316
443
|
*/
|
|
317
444
|
update(): void;
|
|
318
445
|
}
|
|
@@ -351,6 +478,16 @@ export interface Scope extends Disposable {
|
|
|
351
478
|
* });
|
|
352
479
|
* }
|
|
353
480
|
* ```
|
|
481
|
+
*
|
|
482
|
+
* @remarks
|
|
483
|
+
* The root scope: disposing it is what stopping the plugin does. Besides
|
|
484
|
+
* the modules of {@link Scope}, it carries the plugin's `id` and its
|
|
485
|
+
* `dataDirectory`.
|
|
486
|
+
*
|
|
487
|
+
* @capability That of each module (see the modules).
|
|
488
|
+
* @see [How a plugin runs](../../docs/concepts/plugins.md)
|
|
489
|
+
* @since 1.0.0
|
|
490
|
+
* @category Plugin
|
|
354
491
|
*/
|
|
355
492
|
export interface Plugin extends Scope {
|
|
356
493
|
/** The manifest id, e.g. `"community.bookmark"`. */
|
|
@@ -2422,7 +2559,12 @@ export interface Panel extends View, Disposable {
|
|
|
2422
2559
|
modifiers: DropModifiers;
|
|
2423
2560
|
}) => unknown): Disposable;
|
|
2424
2561
|
}
|
|
2425
|
-
/**
|
|
2562
|
+
/**
|
|
2563
|
+
* Keys held when the files were dropped.
|
|
2564
|
+
*
|
|
2565
|
+
* @since 1.0.0
|
|
2566
|
+
* @category UI
|
|
2567
|
+
*/
|
|
2426
2568
|
export interface DropModifiers {
|
|
2427
2569
|
/** Option (macOS) / Alt (Windows). */
|
|
2428
2570
|
readonly alt: boolean;
|
|
@@ -2431,7 +2573,13 @@ export interface DropModifiers {
|
|
|
2431
2573
|
/** ⌘ on macOS, Ctrl on Windows. */
|
|
2432
2574
|
readonly cmd: boolean;
|
|
2433
2575
|
}
|
|
2434
|
-
/**
|
|
2576
|
+
/**
|
|
2577
|
+
* A provider for files dropped on the video area ({@link Drops.provide}).
|
|
2578
|
+
*
|
|
2579
|
+
* @see {@link Drops.provide}
|
|
2580
|
+
* @since 1.0.0
|
|
2581
|
+
* @category UI
|
|
2582
|
+
*/
|
|
2435
2583
|
export interface DropHandler {
|
|
2436
2584
|
/**
|
|
2437
2585
|
* Lower case, without the dot; `"folder"` matches directories.
|
|
@@ -2444,7 +2592,39 @@ export interface DropHandler {
|
|
|
2444
2592
|
modifiers: DropModifiers;
|
|
2445
2593
|
}): unknown;
|
|
2446
2594
|
}
|
|
2447
|
-
/**
|
|
2595
|
+
/**
|
|
2596
|
+
* `plugin.drops`: files dropped on the video area. Needs `ui.drop`.
|
|
2597
|
+
*
|
|
2598
|
+
* @remarks
|
|
2599
|
+
* Files dropped on the video area go to providers by extension: each
|
|
2600
|
+
* extension belongs to the provider registered for it first, and each
|
|
2601
|
+
* provider gets one call with all of its paths. Files nobody takes are
|
|
2602
|
+
* ignored. A panel with {@link Panel.onDrop} takes drops on itself first.
|
|
2603
|
+
*
|
|
2604
|
+
* @capability ui.drop
|
|
2605
|
+
*
|
|
2606
|
+
* @example Collect dropped CHIP-8 programs
|
|
2607
|
+
* `.ch8` files dropped on the player are added to the plugin's list. The
|
|
2608
|
+
* provider claims the extension; the player calls `drop` with the dropped
|
|
2609
|
+
* paths of that type, in the order the file manager gave them. Extensions
|
|
2610
|
+
* go to the first provider: the built-in playlist claims every audio and
|
|
2611
|
+
* video extension when it starts, and a built-in plugin cannot be disabled,
|
|
2612
|
+
* so another plugin's provider for those never gets them (except a
|
|
2613
|
+
* development load that replaces the playlist under its id).
|
|
2614
|
+
* ```ts
|
|
2615
|
+
* const queue: string[] = [];
|
|
2616
|
+
* plugin.drops.provide({
|
|
2617
|
+
* extensions: ["ch8"],
|
|
2618
|
+
* drop: ({ paths }) => {
|
|
2619
|
+
* queue.push(...paths); // only .ch8 files reach here
|
|
2620
|
+
* },
|
|
2621
|
+
* });
|
|
2622
|
+
* ```
|
|
2623
|
+
*
|
|
2624
|
+
* @see {@link Panel.onDrop}
|
|
2625
|
+
* @since 1.0.0
|
|
2626
|
+
* @category UI
|
|
2627
|
+
*/
|
|
2448
2628
|
export interface Drops {
|
|
2449
2629
|
/**
|
|
2450
2630
|
* Take files dropped on the video area. The first provider registered for
|
|
@@ -2478,6 +2658,9 @@ export interface Drops {
|
|
|
2478
2658
|
* },
|
|
2479
2659
|
* });
|
|
2480
2660
|
* ```
|
|
2661
|
+
*
|
|
2662
|
+
* @capability ui.drop
|
|
2663
|
+
* @since 1.0.0
|
|
2481
2664
|
*/
|
|
2482
2665
|
provide(handler: DropHandler): Disposable;
|
|
2483
2666
|
}
|
|
@@ -3394,9 +3577,19 @@ export interface Panels {
|
|
|
3394
3577
|
/** The sidebar position, including its floating state; any plugin may read and toggle it. */
|
|
3395
3578
|
readonly sidebar: SidebarSlot;
|
|
3396
3579
|
}
|
|
3397
|
-
/**
|
|
3580
|
+
/**
|
|
3581
|
+
* What a timeline mark stands for; readers choose colours by it.
|
|
3582
|
+
*
|
|
3583
|
+
* @since 1.0.0
|
|
3584
|
+
* @category Playback
|
|
3585
|
+
*/
|
|
3398
3586
|
export type MarkKind = "chapter" | "bookmark" | "marker";
|
|
3399
|
-
/**
|
|
3587
|
+
/**
|
|
3588
|
+
* One mark on the seek bar.
|
|
3589
|
+
*
|
|
3590
|
+
* @since 1.0.0
|
|
3591
|
+
* @category Playback
|
|
3592
|
+
*/
|
|
3400
3593
|
export interface TimelineMark {
|
|
3401
3594
|
/** Seconds from the start. */
|
|
3402
3595
|
readonly time: number;
|
|
@@ -3407,7 +3600,28 @@ export interface TimelineMark {
|
|
|
3407
3600
|
/** The manifest id of the plugin that published it. */
|
|
3408
3601
|
readonly plugin: string;
|
|
3409
3602
|
}
|
|
3410
|
-
/**
|
|
3603
|
+
/**
|
|
3604
|
+
* This plugin's own marks; disposing it removes them.
|
|
3605
|
+
*
|
|
3606
|
+
* @remarks
|
|
3607
|
+
* This plugin's marks on the seek bar, from {@link Timeline.layer}. `set`
|
|
3608
|
+
* replaces the whole layer and never throws: the newest write wins, and a
|
|
3609
|
+
* refused one is reported to `onError`. Disposing the layer, its scope or
|
|
3610
|
+
* stopping the plugin removes the marks.
|
|
3611
|
+
*
|
|
3612
|
+
* @capability timeline.marks
|
|
3613
|
+
*
|
|
3614
|
+
* @example Clear the plugin's marks
|
|
3615
|
+
* The plugin's marks disappear from the seek bar while it keeps its
|
|
3616
|
+
* layer for later. An empty list clears the layer; other plugins' marks
|
|
3617
|
+
* stay.
|
|
3618
|
+
* ```ts
|
|
3619
|
+
* plugin.timeline.layer().set([]);
|
|
3620
|
+
* ```
|
|
3621
|
+
*
|
|
3622
|
+
* @since 1.0.0
|
|
3623
|
+
* @category Playback
|
|
3624
|
+
*/
|
|
3411
3625
|
export interface TimelineLayer extends Disposable {
|
|
3412
3626
|
/**
|
|
3413
3627
|
* Replace the layer (last write wins; `[]` clears it). At most 1000 marks,
|
|
@@ -3432,6 +3646,9 @@ export interface TimelineLayer extends Disposable {
|
|
|
3432
3646
|
* { ifCurrent: seen },
|
|
3433
3647
|
* );
|
|
3434
3648
|
* ```
|
|
3649
|
+
*
|
|
3650
|
+
* @capability timeline.marks
|
|
3651
|
+
* @since 1.0.0
|
|
3435
3652
|
*/
|
|
3436
3653
|
set(marks: {
|
|
3437
3654
|
time: number;
|
|
@@ -3444,16 +3661,79 @@ export interface TimelineLayer extends Disposable {
|
|
|
3444
3661
|
/**
|
|
3445
3662
|
* `plugin.timeline`: marks on the seek bar. Every plugin publishes one
|
|
3446
3663
|
* layer; the player merges them. A new source clears every layer.
|
|
3664
|
+
*
|
|
3665
|
+
* @remarks
|
|
3666
|
+
* Marks on the player's seek bar: chapters, bookmarks, loop points. Each
|
|
3667
|
+
* plugin publishes one layer with {@link Timeline.layer}; the player merges
|
|
3668
|
+
* the layers by time and shows them. A new source clears every layer, so a
|
|
3669
|
+
* plugin publishes again for each file. `marks` is the merged list, which
|
|
3670
|
+
* any plugin can read.
|
|
3671
|
+
*
|
|
3672
|
+
* @capability `timeline.marks` to publish; `playback.read` to read;
|
|
3673
|
+
* `events` to follow changes.
|
|
3674
|
+
*
|
|
3675
|
+
* @example Count the chapter marks other plugins published
|
|
3676
|
+
* The plugin reads how many chapter marks the seek bar shows, whoever
|
|
3677
|
+
* published them. `marks` is the merged list of every layer, sorted by
|
|
3678
|
+
* time, with each mark's `plugin`.
|
|
3679
|
+
* ```ts
|
|
3680
|
+
* const chapters = plugin.timeline.marks.filter((mark) => mark.kind === "chapter");
|
|
3681
|
+
* await plugin.osd.show("{} chapters", [chapters.length]);
|
|
3682
|
+
* ```
|
|
3683
|
+
*
|
|
3684
|
+
* @since 1.0.0
|
|
3685
|
+
* @category Playback
|
|
3447
3686
|
*/
|
|
3448
3687
|
export interface Timeline {
|
|
3449
3688
|
/** Every layer merged, by time (`playback.read`). Empty on a new source. */
|
|
3450
3689
|
readonly marks: readonly TimelineMark[];
|
|
3451
|
-
/**
|
|
3690
|
+
/**
|
|
3691
|
+
* The merged list changed (needs `events`; at most every 100 ms).
|
|
3692
|
+
*
|
|
3693
|
+
* @capability events and playback.read
|
|
3694
|
+
*
|
|
3695
|
+
* @example Follow the marks as they change
|
|
3696
|
+
* A panel lists the seek bar's marks and updates when any plugin publishes
|
|
3697
|
+
* or a new file clears them. The listener gets the merged list, at most
|
|
3698
|
+
* every 100 ms.
|
|
3699
|
+
* ```ts
|
|
3700
|
+
* plugin.timeline.onChange((marks) => {
|
|
3701
|
+
* console.log(marks.map((mark) => `${mark.time}s ${mark.title ?? mark.kind}`));
|
|
3702
|
+
* });
|
|
3703
|
+
* ```
|
|
3704
|
+
*
|
|
3705
|
+
* @since 1.0.0
|
|
3706
|
+
*/
|
|
3452
3707
|
onChange(listener: (marks: readonly TimelineMark[]) => void): Disposable;
|
|
3453
|
-
/**
|
|
3708
|
+
/**
|
|
3709
|
+
* This plugin's layer (`timeline.marks`); a second call throws.
|
|
3710
|
+
*
|
|
3711
|
+
* @capability timeline.marks
|
|
3712
|
+
*
|
|
3713
|
+
* @example Publish this plugin's marks
|
|
3714
|
+
* The plugin gets its one layer and puts two chapter marks on the seek
|
|
3715
|
+
* bar. Calling `layer()` again while the first layer is not disposed
|
|
3716
|
+
* throws; disposing the layer removes the
|
|
3717
|
+
* marks.
|
|
3718
|
+
* ```ts
|
|
3719
|
+
* const layer = plugin.timeline.layer();
|
|
3720
|
+
* layer.set([
|
|
3721
|
+
* { time: 0, kind: "chapter", title: "Intro" },
|
|
3722
|
+
* { time: 95, kind: "chapter", title: "Part 1" }, // seconds
|
|
3723
|
+
* ]);
|
|
3724
|
+
* ```
|
|
3725
|
+
*
|
|
3726
|
+
* @since 1.0.0
|
|
3727
|
+
*/
|
|
3454
3728
|
layer(): TimelineLayer;
|
|
3455
3729
|
}
|
|
3456
|
-
/**
|
|
3730
|
+
/**
|
|
3731
|
+
* The main window as `plugin.window.state` reports it (logical points).
|
|
3732
|
+
*
|
|
3733
|
+
* @see {@link MainWindow.state}
|
|
3734
|
+
* @since 1.0.0
|
|
3735
|
+
* @category Plugin
|
|
3736
|
+
*/
|
|
3457
3737
|
export interface WindowState {
|
|
3458
3738
|
/** The main window is fullscreen. */
|
|
3459
3739
|
readonly fullscreen: boolean;
|
|
@@ -3503,11 +3783,38 @@ export interface WindowState {
|
|
|
3503
3783
|
* },
|
|
3504
3784
|
* });
|
|
3505
3785
|
* ```
|
|
3786
|
+
*
|
|
3787
|
+
* @remarks
|
|
3788
|
+
* `plugin.window` is the player's main window: its size, fullscreen,
|
|
3789
|
+
* picture-in-picture and the video area. Reading it needs no capability;
|
|
3790
|
+
* changing it needs `window.control`. Changes that last only while the
|
|
3791
|
+
* plugin runs (always on top, pointer auto-hide) revert when it stops.
|
|
3792
|
+
*
|
|
3793
|
+
* @capability None to read; window.control to change; events to follow
|
|
3794
|
+
* outside changes.
|
|
3795
|
+
* @since 1.0.0
|
|
3796
|
+
* @category Plugin
|
|
3506
3797
|
*/
|
|
3507
3798
|
export interface MainWindow {
|
|
3508
3799
|
/** Latest state; zero video area without a native window. */
|
|
3509
3800
|
readonly state: WindowState;
|
|
3510
|
-
/**
|
|
3801
|
+
/**
|
|
3802
|
+
* The window state changed (needs `events` for outside changes).
|
|
3803
|
+
*
|
|
3804
|
+
* @capability events (for outside changes)
|
|
3805
|
+
*
|
|
3806
|
+
* @example Fit a panel to fullscreen
|
|
3807
|
+
* The plugin's panel uses larger text while the player is fullscreen. The
|
|
3808
|
+
* listener runs after the SDK updated `state` from the player's events
|
|
3809
|
+
* (with `events`; without it only after this plugin's own changes).
|
|
3810
|
+
* ```ts
|
|
3811
|
+
* declare const bar: Panel;
|
|
3812
|
+
* // ---cut---
|
|
3813
|
+
* plugin.window.onChange((state) => bar.set({ large: state.fullscreen }));
|
|
3814
|
+
* ```
|
|
3815
|
+
*
|
|
3816
|
+
* @since 1.0.0
|
|
3817
|
+
*/
|
|
3511
3818
|
onChange(listener: (state: WindowState) => void): Disposable;
|
|
3512
3819
|
/**
|
|
3513
3820
|
* Resize the window so the video column is `width`×`height` (the bottom bar
|
|
@@ -3519,6 +3826,23 @@ export interface MainWindow {
|
|
|
3519
3826
|
* Sizes are 1–16384 logical points; the minimum video area is 318×120.
|
|
3520
3827
|
* The visible area excludes the menu bar and Dock on macOS; on Windows it
|
|
3521
3828
|
* is the whole display. `unavailable` without a user interface too.
|
|
3829
|
+
*
|
|
3830
|
+
* @capability window.control
|
|
3831
|
+
* @throws `unavailable` (rejects) Fullscreen, or no user interface.
|
|
3832
|
+
* @throws `invalid` (rejects) A size outside 1–16384 logical points.
|
|
3833
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
3834
|
+
* `window.control`.
|
|
3835
|
+
*
|
|
3836
|
+
* @example Make the video area 1280×720 points
|
|
3837
|
+
* The window resizes so that the video column is 1280×720 logical points,
|
|
3838
|
+
* the bars and a docked sidebar added around it. A request that does not
|
|
3839
|
+
* fit the screen is scaled down keeping the aspect, and `clamped` says so.
|
|
3840
|
+
* ```ts
|
|
3841
|
+
* const applied = await plugin.window.setVideoArea({ width: 1280, height: 720 });
|
|
3842
|
+
* if (applied.clamped) await plugin.osd.show("{}×{}", [applied.width, applied.height]);
|
|
3843
|
+
* ```
|
|
3844
|
+
*
|
|
3845
|
+
* @since 1.0.0
|
|
3522
3846
|
*/
|
|
3523
3847
|
setVideoArea(size: {
|
|
3524
3848
|
width: number;
|
|
@@ -3534,12 +3858,53 @@ export interface MainWindow {
|
|
|
3534
3858
|
/**
|
|
3535
3859
|
* Float the player's windows above other applications. Newest call wins; reverts when this plugin stops.
|
|
3536
3860
|
* `unavailable` without a user interface.
|
|
3861
|
+
*
|
|
3862
|
+
* @capability window.control
|
|
3863
|
+
* @throws `unavailable` (rejects) No user interface.
|
|
3864
|
+
* @throws `invalid` (rejects) `onTop` is not a boolean.
|
|
3865
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
3866
|
+
* `window.control`.
|
|
3867
|
+
*
|
|
3868
|
+
* @example Keep the player above other windows
|
|
3869
|
+
* The player's windows (tool windows too) float above other applications
|
|
3870
|
+
* while the plugin's "Always on top" item is checked. The newest call of
|
|
3871
|
+
* any plugin wins, and the setting reverts when this plugin stops.
|
|
3872
|
+
* ```ts
|
|
3873
|
+
* let onTop = false;
|
|
3874
|
+
* plugin.commands.add({
|
|
3875
|
+
* id: "on-top",
|
|
3876
|
+
* path: ["@screen", "Always on top"],
|
|
3877
|
+
* checked: () => onTop,
|
|
3878
|
+
* run: async () => {
|
|
3879
|
+
* onTop = !onTop;
|
|
3880
|
+
* await plugin.window.setAlwaysOnTop(onTop); // reverts when this plugin stops
|
|
3881
|
+
* },
|
|
3882
|
+
* });
|
|
3883
|
+
* ```
|
|
3884
|
+
*
|
|
3885
|
+
* @since 1.0.0
|
|
3537
3886
|
*/
|
|
3538
3887
|
setAlwaysOnTop(onTop: boolean): Promise<void>;
|
|
3539
3888
|
/**
|
|
3540
3889
|
* `"playing"`: hide the pointer over the video after 3 idle seconds while
|
|
3541
3890
|
* playing, in a window too (not only fullscreen). Newest call wins;
|
|
3542
3891
|
* reverts to `"fullscreen"` when this plugin stops.
|
|
3892
|
+
*
|
|
3893
|
+
* @capability window.control
|
|
3894
|
+
* @throws `invalid` (rejects) A mode other than `"fullscreen"` or
|
|
3895
|
+
* `"playing"`.
|
|
3896
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
3897
|
+
* `window.control`.
|
|
3898
|
+
*
|
|
3899
|
+
* @example Hide the pointer over playing video in a window too
|
|
3900
|
+
* The pointer disappears after 3 idle seconds over playing video, not only
|
|
3901
|
+
* in fullscreen. The newest call wins; when this plugin stops the player
|
|
3902
|
+
* goes back to hiding it in fullscreen only.
|
|
3903
|
+
* ```ts
|
|
3904
|
+
* await plugin.window.setCursorAutoHide("playing");
|
|
3905
|
+
* ```
|
|
3906
|
+
*
|
|
3907
|
+
* @since 1.0.0
|
|
3543
3908
|
*/
|
|
3544
3909
|
setCursorAutoHide(mode: "fullscreen" | "playing"): Promise<void>;
|
|
3545
3910
|
/**
|
|
@@ -3564,10 +3929,26 @@ export interface MainWindow {
|
|
|
3564
3929
|
* if (plugin.window.state.pictureInPicture.supported)
|
|
3565
3930
|
* await plugin.window.setPictureInPicture(!plugin.window.state.pictureInPicture.active);
|
|
3566
3931
|
* ```
|
|
3932
|
+
*
|
|
3933
|
+
* @capability window.control
|
|
3934
|
+
* @throws `unsupported` (rejects) The system has no picture-in-picture
|
|
3935
|
+
* (`supported` is false).
|
|
3936
|
+
* @throws `unavailable` (rejects) No video, or no user interface.
|
|
3937
|
+
* @throws `invalid` (rejects) `on` is not a boolean.
|
|
3938
|
+
* @throws `busy` (rejects) The previous call is still switching.
|
|
3939
|
+
* @throws `failed` (rejects) The system refused.
|
|
3940
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
3941
|
+
* `window.control`.
|
|
3942
|
+
* @since 1.0.0
|
|
3567
3943
|
*/
|
|
3568
3944
|
setPictureInPicture(on: boolean): Promise<void>;
|
|
3569
3945
|
}
|
|
3570
|
-
/**
|
|
3946
|
+
/**
|
|
3947
|
+
* `plugin.app`: fixed for the life of the plugin process.
|
|
3948
|
+
*
|
|
3949
|
+
* @since 1.0.0
|
|
3950
|
+
* @category Plugin
|
|
3951
|
+
*/
|
|
3571
3952
|
export interface AppInfo {
|
|
3572
3953
|
/** The player's version, e.g. `"1.0.0"`. */
|
|
3573
3954
|
readonly version: string;
|
|
@@ -3597,15 +3978,47 @@ export interface AppInfo {
|
|
|
3597
3978
|
* const system = await plugin.app.system();
|
|
3598
3979
|
* const text = `Vestra ${plugin.app.version} on ${system.name} ${system.version ?? ""}`;
|
|
3599
3980
|
* ```
|
|
3981
|
+
*
|
|
3982
|
+
* @remarks
|
|
3983
|
+
* `plugin.app` holds facts fixed for the player's run (its version, build,
|
|
3984
|
+
* library versions) and reads the system's on request: `system()` reads
|
|
3985
|
+
* the displays at each call; the operating system's name and version are
|
|
3986
|
+
* worked out once, at the first call, and reused.
|
|
3987
|
+
*
|
|
3988
|
+
* @capability None.
|
|
3989
|
+
* @since 1.0.0
|
|
3990
|
+
* @category Plugin
|
|
3600
3991
|
*/
|
|
3601
3992
|
export interface App extends AppInfo {
|
|
3602
3993
|
/**
|
|
3603
3994
|
* The operating system and the displays, read when called (displays come
|
|
3604
3995
|
* and go). No capability. CPU and memory: Node's `os` module.
|
|
3996
|
+
*
|
|
3997
|
+
* @capability None.
|
|
3998
|
+
* @throws `busy` (rejects) The player could not start the request (no
|
|
3999
|
+
* thread available).
|
|
4000
|
+
*
|
|
4001
|
+
* @example Choose a size for the primary display
|
|
4002
|
+
* The plugin sizes its picture for the primary display. `system()` asks
|
|
4003
|
+
* the player; `displays` lists the displays present now, the primary one
|
|
4004
|
+
* first (empty without a user interface).
|
|
4005
|
+
* ```ts
|
|
4006
|
+
* const { displays } = await plugin.app.system();
|
|
4007
|
+
* const primary = displays[0];
|
|
4008
|
+
* if (primary) console.log(primary.width, primary.height);
|
|
4009
|
+
* ```
|
|
4010
|
+
*
|
|
4011
|
+
* @since 1.0.0
|
|
3605
4012
|
*/
|
|
3606
4013
|
system(): Promise<SystemInfo>;
|
|
3607
4014
|
}
|
|
3608
|
-
/**
|
|
4015
|
+
/**
|
|
4016
|
+
* What {@link App.system} reports.
|
|
4017
|
+
*
|
|
4018
|
+
* @see {@link App.system}
|
|
4019
|
+
* @since 1.0.0
|
|
4020
|
+
* @category Plugin
|
|
4021
|
+
*/
|
|
3609
4022
|
export interface SystemInfo {
|
|
3610
4023
|
/** The operating system family. */
|
|
3611
4024
|
readonly os: "macos" | "windows" | "linux" | "other";
|
|
@@ -3622,7 +4035,12 @@ export interface SystemInfo {
|
|
|
3622
4035
|
/** Primary display first; empty without a user interface. */
|
|
3623
4036
|
readonly displays: readonly DisplayInfo[];
|
|
3624
4037
|
}
|
|
3625
|
-
/**
|
|
4038
|
+
/**
|
|
4039
|
+
* One display in {@link SystemInfo.displays}.
|
|
4040
|
+
*
|
|
4041
|
+
* @since 1.0.0
|
|
4042
|
+
* @category Plugin
|
|
4043
|
+
*/
|
|
3626
4044
|
export interface DisplayInfo {
|
|
3627
4045
|
/** The system's name for it, `null` when it has none. */
|
|
3628
4046
|
readonly name: string | null;
|
|
@@ -5630,6 +6048,9 @@ export interface Osd {
|
|
|
5630
6048
|
* The state of a filter, display layer or render options handle.
|
|
5631
6049
|
* After `failed` nothing retries automatically until the next `set()`,
|
|
5632
6050
|
* `retry()` or a new source.
|
|
6051
|
+
*
|
|
6052
|
+
* @since 1.0.0
|
|
6053
|
+
* @category Processing
|
|
5633
6054
|
*/
|
|
5634
6055
|
export type FilterStatus =
|
|
5635
6056
|
/** No ready media; the value is kept and applied when media is ready. */
|
|
@@ -5705,6 +6126,18 @@ interface Filter<T> extends Disposable {
|
|
|
5705
6126
|
* picture.onStatus((s) => s.state === "failed" && plugin.osd.show(s.error.message));
|
|
5706
6127
|
* picture.set([{ kind: "eq", brightness: 0.1, contrast: 1.1, saturation: 1, gamma: 1 }]);
|
|
5707
6128
|
* ```
|
|
6129
|
+
*
|
|
6130
|
+
* @remarks
|
|
6131
|
+
* Members: `set(nodes)` records the desired chain (last write wins) and
|
|
6132
|
+
* sends it, one request at a time and only the newest; `status` and
|
|
6133
|
+
* `onStatus` report `waiting`, `applying`, `applied` or `failed`; `retry`
|
|
6134
|
+
* applies the current value again; `dispose` withdraws the chain. `set`
|
|
6135
|
+
* never throws: a refused chain shows as `failed` in `status`.
|
|
6136
|
+
*
|
|
6137
|
+
* @capability processing.video, playback.read and events
|
|
6138
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6139
|
+
* @since 1.0.0
|
|
6140
|
+
* @category Processing
|
|
5708
6141
|
*/
|
|
5709
6142
|
export type VideoFilter = Filter<VideoNode[]>;
|
|
5710
6143
|
/**
|
|
@@ -5724,6 +6157,16 @@ export type VideoFilter = Filter<VideoNode[]>;
|
|
|
5724
6157
|
* const audio = plugin.audio.filter("sound");
|
|
5725
6158
|
* audio.set({ nodes: [{ kind: "volume", gainDb: -6 }], delayMs: 120 }); // 10 ms steps
|
|
5726
6159
|
* ```
|
|
6160
|
+
*
|
|
6161
|
+
* @remarks
|
|
6162
|
+
* The same members and rules as {@link VideoFilter}: `set` records the
|
|
6163
|
+
* desired value and never throws, `status` and `onStatus` report the
|
|
6164
|
+
* result, `retry` applies it again, `dispose` withdraws it.
|
|
6165
|
+
*
|
|
6166
|
+
* @capability processing.audio, playback.read and events
|
|
6167
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6168
|
+
* @since 1.0.0
|
|
6169
|
+
* @category Processing
|
|
5727
6170
|
*/
|
|
5728
6171
|
export type AudioFilter = Filter<{
|
|
5729
6172
|
/** The audio chain; `[]` for none. */
|
|
@@ -5734,6 +6177,9 @@ export type AudioFilter = Filter<{
|
|
|
5734
6177
|
/**
|
|
5735
6178
|
* How the video sits in the video area; not part of snapshots or exports.
|
|
5736
6179
|
* Defaults: `{ aspect: null, fit: "keep", zoom: 1, panX: 0, panY: 0 }`.
|
|
6180
|
+
*
|
|
6181
|
+
* @since 1.0.0
|
|
6182
|
+
* @category Processing
|
|
5737
6183
|
*/
|
|
5738
6184
|
export interface ViewSettings {
|
|
5739
6185
|
/** Display aspect override 0.25–4; `null` = as the source. */
|
|
@@ -5761,24 +6207,73 @@ export interface ViewSettings {
|
|
|
5761
6207
|
* The video fills the video area (cropped instead of letterboxed), then
|
|
5762
6208
|
* zooms to 150 % shifted a little to the right. The first plugin to call
|
|
5763
6209
|
* `view()` owns the display layer; each `set` merges into its settings,
|
|
5764
|
-
* which stay across sources until the plugin
|
|
6210
|
+
* which stay across sources until the plugin disposes the view or stops.
|
|
5765
6211
|
* ```ts
|
|
5766
6212
|
* const view = plugin.video.view();
|
|
5767
6213
|
* view.set({ fit: "fill" });
|
|
5768
6214
|
* view.set({ zoom: 1.5, panX: 0.1 }); // merged with the previous write
|
|
5769
6215
|
* ```
|
|
6216
|
+
*
|
|
6217
|
+
* @remarks
|
|
6218
|
+
* Not part of snapshots or exports: it changes how the picture sits in the
|
|
6219
|
+
* video area, not the picture. `set` merges into the current settings and
|
|
6220
|
+
* never throws: an out-of-range value or unknown field shows as `failed`
|
|
6221
|
+
* (`invalid`) in `status` and is reported to `onError`, without sending
|
|
6222
|
+
* anything.
|
|
6223
|
+
*
|
|
6224
|
+
* @capability processing.video, playback.read and events (like
|
|
6225
|
+
* {@link VideoFilter})
|
|
6226
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6227
|
+
* @since 1.0.0
|
|
6228
|
+
* @category Processing
|
|
5770
6229
|
*/
|
|
5771
6230
|
export interface VideoView extends Disposable {
|
|
5772
|
-
/**
|
|
6231
|
+
/**
|
|
6232
|
+
* Merged into the current settings; last write wins.
|
|
6233
|
+
*
|
|
6234
|
+
* @capability processing.video
|
|
6235
|
+
*
|
|
6236
|
+
* @example Stretch a 4:3 video to fill the window
|
|
6237
|
+
* A 4:3 video fills a 16:9 window, distorted, as the user asked. Each
|
|
6238
|
+
* `set` merges the given fields into this plugin's view settings; the
|
|
6239
|
+
* newest write wins, and the settings stay for later files until the
|
|
6240
|
+
* plugin disposes the view or stops.
|
|
6241
|
+
* ```ts
|
|
6242
|
+
* plugin.video.view().set({ fit: "stretch" });
|
|
6243
|
+
* ```
|
|
6244
|
+
*
|
|
6245
|
+
* @since 1.0.0
|
|
6246
|
+
*/
|
|
5773
6247
|
set(value: Partial<ViewSettings>): void;
|
|
5774
6248
|
/** Current status (the same four states as filters). */
|
|
5775
6249
|
readonly status: FilterStatus;
|
|
5776
|
-
/**
|
|
6250
|
+
/**
|
|
6251
|
+
* The status changed.
|
|
6252
|
+
*
|
|
6253
|
+
* @capability processing.video
|
|
6254
|
+
*
|
|
6255
|
+
* @example Tell the user another plugin owns the view
|
|
6256
|
+
* When another plugin already controls the display layer, the OSD says
|
|
6257
|
+
* so. The first plugin to call `view()` owns it; this plugin's handle
|
|
6258
|
+
* then reports `failed` with `conflict`, and tries again on its next `set`.
|
|
6259
|
+
* ```ts
|
|
6260
|
+
* const view = plugin.video.view();
|
|
6261
|
+
* view.onStatus((status) => {
|
|
6262
|
+
* if (status.state === "failed" && status.error.code === "conflict")
|
|
6263
|
+
* void plugin.osd.show("Another plugin controls the picture size");
|
|
6264
|
+
* });
|
|
6265
|
+
* ```
|
|
6266
|
+
*
|
|
6267
|
+
* @since 1.0.0
|
|
6268
|
+
*/
|
|
5777
6269
|
onStatus(listener: (status: FilterStatus) => void): Disposable;
|
|
5778
6270
|
}
|
|
5779
6271
|
/**
|
|
5780
6272
|
* GPU renderer options; not part of snapshots or exports.
|
|
5781
6273
|
* Defaults: `{ toneMapping: "auto", targetPeak: 203, peakDetect: true, shaders: [], interpolation: false }`.
|
|
6274
|
+
*
|
|
6275
|
+
* @since 1.0.0
|
|
6276
|
+
* @category Processing
|
|
5782
6277
|
*/
|
|
5783
6278
|
export interface RenderSettings {
|
|
5784
6279
|
/** HDR → SDR: "auto" maps HDR sources only; "off" clips without a curve. */
|
|
@@ -5817,24 +6312,74 @@ export interface RenderSettings {
|
|
|
5817
6312
|
* });
|
|
5818
6313
|
* render.set({ toneMapping: "bt.2390", targetPeak: 250 }); // nits
|
|
5819
6314
|
* ```
|
|
6315
|
+
*
|
|
6316
|
+
* @remarks
|
|
6317
|
+
* GPU renderer options (tone mapping, peak detection, shaders,
|
|
6318
|
+
* interpolation). Only the GPU renderer has them, which today means macOS;
|
|
6319
|
+
* under software rendering the handle is `failed` with `unsupported` after
|
|
6320
|
+
* the player's first answer to `render()` (reported to `onError` too), and so is
|
|
6321
|
+
* every `set`. Single owner, like {@link VideoView}. `set` never throws; the
|
|
6322
|
+
* result shows in `status`.
|
|
6323
|
+
*
|
|
6324
|
+
* @capability processing.video, playback.read and events (like
|
|
6325
|
+
* {@link VideoFilter})
|
|
6326
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6327
|
+
* @since 1.0.0
|
|
6328
|
+
* @category Processing
|
|
5820
6329
|
*/
|
|
5821
6330
|
export interface RenderOptions extends Disposable {
|
|
5822
|
-
/**
|
|
6331
|
+
/**
|
|
6332
|
+
* Merged into the current settings; last write wins.
|
|
6333
|
+
*
|
|
6334
|
+
* @capability processing.video
|
|
6335
|
+
*
|
|
6336
|
+
* @example Turn on motion interpolation
|
|
6337
|
+
* Motion becomes smoother on the GPU renderer. `set` merges the field into
|
|
6338
|
+
* the plugin's render options; under software rendering the status turns
|
|
6339
|
+
* `failed` with `unsupported` instead.
|
|
6340
|
+
* ```ts
|
|
6341
|
+
* plugin.video.render().set({ interpolation: true });
|
|
6342
|
+
* ```
|
|
6343
|
+
*
|
|
6344
|
+
* @since 1.0.0
|
|
6345
|
+
*/
|
|
5823
6346
|
set(value: Partial<RenderSettings>): void;
|
|
5824
6347
|
/**
|
|
5825
6348
|
* `failed` with `unsupported` while the player renders in software;
|
|
5826
6349
|
* `failed` with `failed` when mpv cannot compile a shader (pushed later).
|
|
5827
6350
|
*/
|
|
5828
6351
|
readonly status: FilterStatus;
|
|
5829
|
-
/**
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
6352
|
+
/**
|
|
6353
|
+
* The status changed (including later pushes such as shader compile errors).
|
|
6354
|
+
*
|
|
6355
|
+
* @capability processing.video
|
|
6356
|
+
*
|
|
6357
|
+
* @example Report a shader that does not compile
|
|
6358
|
+
* The OSD shows mpv's message when a shader the plugin set fails to
|
|
6359
|
+
* compile. The player pushes that failure after the `set` was accepted, so
|
|
6360
|
+
* it arrives through `onStatus`, not as a rejected call.
|
|
6361
|
+
* ```ts
|
|
6362
|
+
* const render = plugin.video.render();
|
|
6363
|
+
* render.onStatus((status) => {
|
|
6364
|
+
* if (status.state === "failed") void plugin.osd.show("Render: {}", [status.error.message]);
|
|
6365
|
+
* });
|
|
6366
|
+
* ```
|
|
6367
|
+
*
|
|
6368
|
+
* @since 1.0.0
|
|
6369
|
+
*/
|
|
6370
|
+
onStatus(listener: (status: FilterStatus) => void): Disposable;
|
|
6371
|
+
}
|
|
6372
|
+
/**
|
|
6373
|
+
* A subtitle track of the current source.
|
|
6374
|
+
*
|
|
6375
|
+
* @since 1.0.0
|
|
6376
|
+
* @category Subtitles
|
|
6377
|
+
*/
|
|
6378
|
+
export interface SubtitleTrack {
|
|
6379
|
+
/** Stable while the track exists; compare tracks by id. */
|
|
6380
|
+
id: string;
|
|
6381
|
+
/** Inside the container, or loaded from a file. */
|
|
6382
|
+
kind: "embedded" | "external";
|
|
5838
6383
|
/** The container's subtitle track number, for embedded tracks only. */
|
|
5839
6384
|
embeddedId?: number;
|
|
5840
6385
|
/** Track title. */
|
|
@@ -5857,7 +6402,13 @@ export interface SubtitleTrack {
|
|
|
5857
6402
|
*/
|
|
5858
6403
|
streamIndex: number | null;
|
|
5859
6404
|
}
|
|
5860
|
-
/**
|
|
6405
|
+
/**
|
|
6406
|
+
* The subtitle state of the current source.
|
|
6407
|
+
*
|
|
6408
|
+
* @see {@link Subtitles.state}
|
|
6409
|
+
* @since 1.0.0
|
|
6410
|
+
* @category Subtitles
|
|
6411
|
+
*/
|
|
5861
6412
|
export interface SubtitlesState {
|
|
5862
6413
|
/** The source it belongs to ({@link PlayerState.sourceId}). */
|
|
5863
6414
|
readonly sourceId: number;
|
|
@@ -5888,7 +6439,12 @@ export interface SubtitlesState {
|
|
|
5888
6439
|
/** The last error, `null` without one. */
|
|
5889
6440
|
readonly error: string | null;
|
|
5890
6441
|
}
|
|
5891
|
-
/**
|
|
6442
|
+
/**
|
|
6443
|
+
* Subtitle calls act on the selection you see; pass `ifCurrent` to pin an older one.
|
|
6444
|
+
*
|
|
6445
|
+
* @since 1.0.0
|
|
6446
|
+
* @category Subtitles
|
|
6447
|
+
*/
|
|
5892
6448
|
export interface SubtitleConditional {
|
|
5893
6449
|
/** The subtitle state the call is conditional on; `stale` otherwise. */
|
|
5894
6450
|
ifCurrent?: SubtitlesState;
|
|
@@ -5922,18 +6478,104 @@ export interface SubtitleConditional {
|
|
|
5922
6478
|
* offsetMs: 0,
|
|
5923
6479
|
* });
|
|
5924
6480
|
* ```
|
|
6481
|
+
*
|
|
6482
|
+
* @remarks
|
|
6483
|
+
* The subtitle tracks of the current source, the primary and secondary
|
|
6484
|
+
* selection, uploaded files and the style. `state` is the SDK's snapshot of
|
|
6485
|
+
* the player's subtitle state (`null` until first read). The player has no
|
|
6486
|
+
* subtitle event: the SDK reads the state again when the source, its phase
|
|
6487
|
+
* or the backend changes (with `events`) and after this plugin's own
|
|
6488
|
+
* changes, so a selection the user or another plugin changed shows only
|
|
6489
|
+
* after `refresh()`. A change without `ifCurrent` reads the state first
|
|
6490
|
+
* and applies to it; pass `ifCurrent` to make it fail with `stale` when the
|
|
6491
|
+
* selection changed since that state. Each change is one operation the
|
|
6492
|
+
* player confirms; `select`, `remove` and `setStyle` resolve with the new
|
|
6493
|
+
* state, `load` with the new track.
|
|
6494
|
+
*
|
|
6495
|
+
* @capability `subtitles.read` to read; `subtitles.control` and
|
|
6496
|
+
* `subtitles.read` to change; `events` to re-read on source changes.
|
|
6497
|
+
*
|
|
6498
|
+
* @see [Load subtitles from a plugin](../../docs/how-to/subtitles.md)
|
|
6499
|
+
* @since 1.0.0
|
|
6500
|
+
* @category Subtitles
|
|
5925
6501
|
*/
|
|
5926
6502
|
export interface Subtitles {
|
|
5927
6503
|
/** `null` until first read. */
|
|
5928
6504
|
readonly state: SubtitlesState | null;
|
|
5929
|
-
/**
|
|
6505
|
+
/**
|
|
6506
|
+
* The subtitle state changed.
|
|
6507
|
+
*
|
|
6508
|
+
* @capability events and subtitles.read
|
|
6509
|
+
*
|
|
6510
|
+
* @example Show the subtitle language after a new file opens
|
|
6511
|
+
* When a new file starts, the OSD names the language of its primary
|
|
6512
|
+
* subtitle. The SDK reads the subtitle state again when the source or its
|
|
6513
|
+
* phase changes (there is no subtitle event, so a selection changed in the
|
|
6514
|
+
* player shows only after `refresh()`) and then calls the listener;
|
|
6515
|
+
* `selected` is `null` when subtitles are off.
|
|
6516
|
+
* ```ts
|
|
6517
|
+
* plugin.subtitles.onChange((state) => {
|
|
6518
|
+
* // A track's language is null when the file does not say.
|
|
6519
|
+
* void plugin.osd.show("Subtitles: {}", [state.selected?.language ?? "off"]);
|
|
6520
|
+
* });
|
|
6521
|
+
* ```
|
|
6522
|
+
*
|
|
6523
|
+
* @since 1.0.0
|
|
6524
|
+
*/
|
|
5930
6525
|
onChange(listener: (state: SubtitlesState) => void): Disposable;
|
|
5931
|
-
/**
|
|
6526
|
+
/**
|
|
6527
|
+
* Read the state from the player now.
|
|
6528
|
+
*
|
|
6529
|
+
* @capability subtitles.read
|
|
6530
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6531
|
+
* `subtitles.read`.
|
|
6532
|
+
*
|
|
6533
|
+
* @example Count the subtitle tracks of the file
|
|
6534
|
+
* The plugin reads how many subtitle tracks the file has before building a
|
|
6535
|
+
* menu. `refresh` asks the player and resolves with the current state, also
|
|
6536
|
+
* without `events`; `state` holds it afterwards.
|
|
6537
|
+
* ```ts
|
|
6538
|
+
* const { tracks } = await plugin.subtitles.refresh();
|
|
6539
|
+
* await plugin.osd.show("{} subtitle tracks", [tracks.length]);
|
|
6540
|
+
* ```
|
|
6541
|
+
*
|
|
6542
|
+
* @since 1.0.0
|
|
6543
|
+
*/
|
|
5932
6544
|
refresh(): Promise<SubtitlesState>;
|
|
5933
6545
|
/**
|
|
5934
6546
|
* `role: "secondary"` sets the secondary subtitle: a text track that is not
|
|
5935
6547
|
* the primary one (`invalid` otherwise). A new source clears it.
|
|
5936
6548
|
* `track: null` turns the role off.
|
|
6549
|
+
*
|
|
6550
|
+
* @capability subtitles.control and subtitles.read
|
|
6551
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source or the subtitle
|
|
6552
|
+
* selection changed since that state.
|
|
6553
|
+
* @throws `invalid` (rejects) An unknown track; as secondary, a bitmap
|
|
6554
|
+
* track or the primary one; as primary, the current secondary.
|
|
6555
|
+
* @throws `missing` (rejects) The track is not in the current source.
|
|
6556
|
+
* @throws `unavailable` (rejects) Nothing loaded, or the source is still
|
|
6557
|
+
* loading or not yet confirmed.
|
|
6558
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6559
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6560
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6561
|
+
*
|
|
6562
|
+
* @example Turn on the first English subtitle
|
|
6563
|
+
* The first English track becomes the primary subtitle, but only in the
|
|
6564
|
+
* file the plugin looked at. Embedded tracks are chosen by number, so
|
|
6565
|
+
* without `ifCurrent` a select after another file opened would pick that
|
|
6566
|
+
* file's track with the same number; with `ifCurrent: state` it fails with
|
|
6567
|
+
* `stale` instead. Without `ifCurrent`, `select` reads the newest state
|
|
6568
|
+
* itself and almost never fails with `stale`.
|
|
6569
|
+
* ```ts
|
|
6570
|
+
* const state = await plugin.subtitles.refresh();
|
|
6571
|
+
* const english = state.tracks.find((track) => track.language === "en");
|
|
6572
|
+
* if (english)
|
|
6573
|
+
* await plugin.subtitles.select(english, { ifCurrent: state }).catch((error: PluginError) => {
|
|
6574
|
+
* if (error.code !== "stale") throw error; // another file or selection now
|
|
6575
|
+
* });
|
|
6576
|
+
* ```
|
|
6577
|
+
*
|
|
6578
|
+
* @since 1.0.0
|
|
5937
6579
|
*/
|
|
5938
6580
|
select(track: SubtitleTrack | null, options?: SubtitleConditional & {
|
|
5939
6581
|
role?: "primary" | "secondary";
|
|
@@ -5950,6 +6592,36 @@ export interface Subtitles {
|
|
|
5950
6592
|
* Limits: SRT ≤ 2 MiB and ≤ 10,000 cues; ASS ≤ 2 MiB with `[Script Info]`
|
|
5951
6593
|
* and `[Events]` and 1–20,000 `Dialogue:` lines. Convert other formats
|
|
5952
6594
|
* (VTT, SAMI) to SRT first.
|
|
6595
|
+
*
|
|
6596
|
+
* @capability subtitles.control and subtitles.read
|
|
6597
|
+
* @throws `invalid` (rejects) A bad `format` or `title`, or a file over the
|
|
6598
|
+
* limits.
|
|
6599
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source changed since
|
|
6600
|
+
* that state.
|
|
6601
|
+
* @throws `unavailable` (rejects) Nothing loaded, or the source is still
|
|
6602
|
+
* loading.
|
|
6603
|
+
* @throws `aborted` (rejects) `signal` aborted before the commit (an
|
|
6604
|
+
* `AbortError`); the upload is discarded.
|
|
6605
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6606
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6607
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6608
|
+
*
|
|
6609
|
+
* @example Load a downloaded SRT and select it
|
|
6610
|
+
* A subtitle file the plugin downloaded becomes a track of the current file
|
|
6611
|
+
* and is selected. The plugin passes the file's bytes; the player parses
|
|
6612
|
+
* them, adds an `external` track for this source only and resolves with
|
|
6613
|
+
* it once the upload is committed.
|
|
6614
|
+
* ```ts
|
|
6615
|
+
* declare const url: string;
|
|
6616
|
+
* // ---cut---
|
|
6617
|
+
* const response = await fetch(url);
|
|
6618
|
+
* if (!response.ok) throw new Error(`subtitles: HTTP ${response.status}`);
|
|
6619
|
+
* const bytes = new Uint8Array(await response.arrayBuffer());
|
|
6620
|
+
* const track = await plugin.subtitles.load(bytes, { title: "Downloaded" }); // selected
|
|
6621
|
+
* await plugin.osd.show("Loaded {}", [track.title]);
|
|
6622
|
+
* ```
|
|
6623
|
+
*
|
|
6624
|
+
* @since 1.0.0
|
|
5953
6625
|
*/
|
|
5954
6626
|
load(bytes: Uint8Array, options?: SubtitleConditional & {
|
|
5955
6627
|
format?: "srt" | "ass";
|
|
@@ -5957,19 +6629,69 @@ export interface Subtitles {
|
|
|
5957
6629
|
select?: "primary" | "secondary" | "none";
|
|
5958
6630
|
signal?: AbortSignal;
|
|
5959
6631
|
}): Promise<SubtitleTrack>;
|
|
5960
|
-
/**
|
|
6632
|
+
/**
|
|
6633
|
+
* Remove a track this plugin loaded.
|
|
6634
|
+
*
|
|
6635
|
+
* @capability subtitles.control and subtitles.read
|
|
6636
|
+
* @throws `invalid` (rejects) The track was not loaded by this plugin.
|
|
6637
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source changed since
|
|
6638
|
+
* that state.
|
|
6639
|
+
* @throws `unavailable` (rejects) Nothing is loaded, or the source is
|
|
6640
|
+
* still loading.
|
|
6641
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6642
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6643
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6644
|
+
*
|
|
6645
|
+
* @example Remove the subtitle the plugin loaded
|
|
6646
|
+
* The plugin's downloaded subtitle disappears from the track list. A plugin
|
|
6647
|
+
* can remove only tracks it loaded itself; a new source removes them
|
|
6648
|
+
* anyway.
|
|
6649
|
+
* ```ts
|
|
6650
|
+
* declare const loaded: SubtitleTrack;
|
|
6651
|
+
* // ---cut---
|
|
6652
|
+
* await plugin.subtitles.remove(loaded);
|
|
6653
|
+
* ```
|
|
6654
|
+
*
|
|
6655
|
+
* @since 1.0.0
|
|
6656
|
+
*/
|
|
5961
6657
|
remove(track: SubtitleTrack, options?: SubtitleConditional): Promise<SubtitlesState>;
|
|
5962
6658
|
/**
|
|
5963
6659
|
* Set this plugin's style layer; `null` removes it. The newest call of
|
|
5964
6660
|
* this plugin replaces its earlier layer. When several plugins have a
|
|
5965
6661
|
* layer, the player uses the one whose manifest id sorts last, whatever
|
|
5966
6662
|
* the order of the calls.
|
|
6663
|
+
*
|
|
6664
|
+
* @capability subtitles.control and subtitles.read
|
|
6665
|
+
* @throws `invalid` (rejects) A field out of range.
|
|
6666
|
+
* @throws `unsupported` (rejects) A font that is not in `state.fonts`, or a
|
|
6667
|
+
* primary subtitle that is a bitmap track.
|
|
6668
|
+
* @throws `stale` (rejects) With `ifCurrent`: the subtitle state changed
|
|
6669
|
+
* since that state.
|
|
6670
|
+
* @throws `unavailable` (rejects) Nothing is loaded, or the source is
|
|
6671
|
+
* still loading.
|
|
6672
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6673
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6674
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6675
|
+
*
|
|
6676
|
+
* @example Take the plugin's style off again
|
|
6677
|
+
* The plugin removes its style layer, so the subtitles go back to the
|
|
6678
|
+
* player's default (or to another plugin's layer). `null` removes only this
|
|
6679
|
+
* plugin's layer; the effective layer is the one whose manifest id sorts
|
|
6680
|
+
* last among the plugins that still have one.
|
|
6681
|
+
* ```ts
|
|
6682
|
+
* await plugin.subtitles.setStyle(null);
|
|
6683
|
+
* ```
|
|
6684
|
+
*
|
|
6685
|
+
* @since 1.0.0
|
|
5967
6686
|
*/
|
|
5968
6687
|
setStyle(style: SubtitleStyle | null, options?: SubtitleConditional): Promise<SubtitlesState>;
|
|
5969
6688
|
}
|
|
5970
6689
|
/**
|
|
5971
6690
|
* Options of {@link MediaCapture.capture}. Conditional on `player.state`
|
|
5972
6691
|
* like other controls.
|
|
6692
|
+
*
|
|
6693
|
+
* @since 1.0.0
|
|
6694
|
+
* @category Media
|
|
5973
6695
|
*/
|
|
5974
6696
|
export interface CaptureOptions extends Conditional {
|
|
5975
6697
|
/** Source time; defaults to the current position at the moment of capture. */
|
|
@@ -5981,7 +6703,36 @@ export interface CaptureOptions extends Conditional {
|
|
|
5981
6703
|
/** Audio to freeze with the snapshot: the selected track, none, or a track id. */
|
|
5982
6704
|
audio?: "selected" | "none" | number;
|
|
5983
6705
|
}
|
|
5984
|
-
/**
|
|
6706
|
+
/**
|
|
6707
|
+
* A frozen, immutable input for exports. Dispose to release it.
|
|
6708
|
+
*
|
|
6709
|
+
* @remarks
|
|
6710
|
+
* A frozen input, from {@link MediaCapture.capture}: the source, the time
|
|
6711
|
+
* and (processed) the effects and subtitle as they were. Exports read it in
|
|
6712
|
+
* the player's offline worker, so one snapshot can be exported several
|
|
6713
|
+
* times and playback is unaffected. Disposing it releases the player's
|
|
6714
|
+
* copy; an export using it after that fails with `released`.
|
|
6715
|
+
*
|
|
6716
|
+
* @capability media.read and media.control
|
|
6717
|
+
*
|
|
6718
|
+
* @example Export one frame as PNG and as JPEG
|
|
6719
|
+
* The same frozen frame is written twice, lossless and compressed. Each
|
|
6720
|
+
* `export` starts a job of its own; the snapshot is released after both
|
|
6721
|
+
* are done.
|
|
6722
|
+
* ```ts
|
|
6723
|
+
* const snapshot = await plugin.media.capture();
|
|
6724
|
+
* try {
|
|
6725
|
+
* const png = await snapshot.export({ format: "png", destination: "/tmp/frame.png" });
|
|
6726
|
+
* const jpeg = await snapshot.export({ format: "jpeg", destination: "/tmp/frame.jpg", quality: 85 });
|
|
6727
|
+
* await Promise.all([png.done, jpeg.done]); // terminal states, not errors
|
|
6728
|
+
* } finally {
|
|
6729
|
+
* await snapshot.dispose();
|
|
6730
|
+
* }
|
|
6731
|
+
* ```
|
|
6732
|
+
*
|
|
6733
|
+
* @since 1.0.0
|
|
6734
|
+
* @category Media
|
|
6735
|
+
*/
|
|
5985
6736
|
export interface Snapshot extends Disposable {
|
|
5986
6737
|
/** Without or with effects. */
|
|
5987
6738
|
readonly kind: "raw" | "processed";
|
|
@@ -6005,6 +6756,32 @@ export interface Snapshot extends Disposable {
|
|
|
6005
6756
|
/**
|
|
6006
6757
|
* Start exporting; the job runs in the host's offline worker.
|
|
6007
6758
|
* One snapshot can be exported several times.
|
|
6759
|
+
*
|
|
6760
|
+
* @capability media.read and media.control
|
|
6761
|
+
* @throws `released` (rejects) The snapshot was already released.
|
|
6762
|
+
* @throws `invalid` (rejects) Bad options (`quality` outside 1–100, a
|
|
6763
|
+
* relative destination, a clip `end` not after the snapshot time or
|
|
6764
|
+
* past the duration).
|
|
6765
|
+
* @throws `busy` (rejects) This plugin runs 2 exports (or the player 8).
|
|
6766
|
+
* @throws `unavailable` (rejects) No FFmpeg, or the export worker could not
|
|
6767
|
+
* start.
|
|
6768
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6769
|
+
* `media.read` and `media.control`.
|
|
6770
|
+
*
|
|
6771
|
+
* @example Export a 30-second clip
|
|
6772
|
+
* A clip from the snapshot time to 30 seconds later is written as MP4.
|
|
6773
|
+
* `export` resolves once the job is accepted; the job then runs in the
|
|
6774
|
+
* player's offline worker, and `done` resolves with its terminal state,
|
|
6775
|
+
* whose `outcome` has the destination or the error.
|
|
6776
|
+
* ```ts
|
|
6777
|
+
* declare const snapshot: Snapshot;
|
|
6778
|
+
* // ---cut---
|
|
6779
|
+
* const job = await snapshot.export({ format: "mp4", destination: "/tmp/clip.mp4", end: snapshot.time + 30 });
|
|
6780
|
+
* const end = await job.done;
|
|
6781
|
+
* if (end.status !== "completed") await plugin.osd.show("Export {}", [end.status]);
|
|
6782
|
+
* ```
|
|
6783
|
+
*
|
|
6784
|
+
* @since 1.0.0
|
|
6008
6785
|
*/
|
|
6009
6786
|
export(options: ExportOptions): Promise<ExportJob>;
|
|
6010
6787
|
}
|
|
@@ -6016,6 +6793,9 @@ export interface Snapshot extends Disposable {
|
|
|
6016
6793
|
* choose the format. `sampleTime` picks the frame (default: the snapshot
|
|
6017
6794
|
* time); `end` is the clip's end in source seconds. On Windows the export
|
|
6018
6795
|
* worker needs FFmpeg 6.1 or newer.
|
|
6796
|
+
*
|
|
6797
|
+
* @since 1.0.0
|
|
6798
|
+
* @category Media
|
|
6019
6799
|
*/
|
|
6020
6800
|
export type ExportOptions = {
|
|
6021
6801
|
/** A lossless frame. */
|
|
@@ -6046,19 +6826,111 @@ export type ExportOptions = {
|
|
|
6046
6826
|
* `completed`, `failed` or `cancelled`; once terminal, `outcome` is
|
|
6047
6827
|
* `{ destination }`, `{ error: { code, message } }` or `{ cancelled: true }`.
|
|
6048
6828
|
* `facts` describes what the worker produced (versions, range, streams).
|
|
6829
|
+
*
|
|
6830
|
+
* @since 1.0.0
|
|
6831
|
+
* @category Media
|
|
6049
6832
|
*/
|
|
6050
6833
|
export type JobState = Omit<MediaJob, "jobId">;
|
|
6051
|
-
/**
|
|
6834
|
+
/**
|
|
6835
|
+
* An export, from {@link Snapshot.export}. Disposing an unfinished job cancels it.
|
|
6836
|
+
*
|
|
6837
|
+
* @remarks
|
|
6838
|
+
* One export, from {@link Snapshot.export}. `state` is the SDK's last copy
|
|
6839
|
+
* of the job's state: the SDK polls the player about every 350 ms until the
|
|
6840
|
+
* job ends, and `refresh` reads it at once. `done` resolves
|
|
6841
|
+
* once the job is terminal (`completed`, `failed` or `cancelled`) and never
|
|
6842
|
+
* rejects for a failed export: the failure is in `outcome`. Disposing an
|
|
6843
|
+
* unfinished job cancels it.
|
|
6844
|
+
*
|
|
6845
|
+
* @capability media.read and media.control
|
|
6846
|
+
*
|
|
6847
|
+
* @example Show export progress and cancel on request
|
|
6848
|
+
* The OSD shows the job's status while it runs, and a Cancel command stops
|
|
6849
|
+
* it. `onChange` follows the state (`queued`, `running`, `cancelling`,
|
|
6850
|
+
* then a terminal one); `cancel` asks the worker to stop. After
|
|
6851
|
+
* `requested` the job usually ends `cancelled`, or `failed` when the
|
|
6852
|
+
* cleanup failed.
|
|
6853
|
+
* ```ts
|
|
6854
|
+
* declare const job: ExportJob;
|
|
6855
|
+
* // ---cut---
|
|
6856
|
+
* job.onChange((state) => void plugin.osd.show("Export: {}", [state.status]));
|
|
6857
|
+
* plugin.commands.add({ id: "cancel-export", title: "Cancel export", run: () => job.cancel() });
|
|
6858
|
+
* await job.done; // completed, failed or cancelled
|
|
6859
|
+
* ```
|
|
6860
|
+
*
|
|
6861
|
+
* @since 1.0.0
|
|
6862
|
+
* @category Media
|
|
6863
|
+
*/
|
|
6052
6864
|
export interface ExportJob extends Disposable {
|
|
6053
6865
|
/** Latest state. */
|
|
6054
6866
|
readonly state: JobState;
|
|
6055
|
-
/**
|
|
6867
|
+
/**
|
|
6868
|
+
* The state changed.
|
|
6869
|
+
*
|
|
6870
|
+
* @capability media.read
|
|
6871
|
+
*
|
|
6872
|
+
* @example Log each state of an export
|
|
6873
|
+
* The plugin's log records each status the job goes through (`queued`,
|
|
6874
|
+
* `running`, `cancelling` after a cancel, then a terminal one). The listener runs when the SDK learns a
|
|
6875
|
+
* new state from its polling.
|
|
6876
|
+
* ```ts
|
|
6877
|
+
* declare const job: ExportJob;
|
|
6878
|
+
* // ---cut---
|
|
6879
|
+
* job.onChange((state) => console.log("export", state.status));
|
|
6880
|
+
* ```
|
|
6881
|
+
*
|
|
6882
|
+
* @since 1.0.0
|
|
6883
|
+
*/
|
|
6056
6884
|
onChange(listener: (state: JobState) => void): Disposable;
|
|
6057
|
-
/**
|
|
6885
|
+
/**
|
|
6886
|
+
* Resolves with the terminal state (completed, failed or cancelled).
|
|
6887
|
+
*
|
|
6888
|
+
* @throws `disposed` (rejects) The job handle was disposed while waiting.
|
|
6889
|
+
*/
|
|
6058
6890
|
readonly done: Promise<JobState>;
|
|
6059
|
-
/**
|
|
6891
|
+
/**
|
|
6892
|
+
* Ask to cancel: `requested`, `tooLate` (already publishing) or `terminal` (already over).
|
|
6893
|
+
*
|
|
6894
|
+
* @capability media.control
|
|
6895
|
+
* @throws `rejected` (rejects) The player did not accept the request.
|
|
6896
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6897
|
+
* `media.control`.
|
|
6898
|
+
*
|
|
6899
|
+
* @example Cancel an export the user no longer wants
|
|
6900
|
+
* The running export stops, unless it is already writing its file.
|
|
6901
|
+
* `cancel` resolves `requested` when the worker will stop (the job ends
|
|
6902
|
+
* `cancelled`), `tooLate` when it is already publishing the file, and
|
|
6903
|
+
* `terminal` when the job had already ended.
|
|
6904
|
+
* ```ts
|
|
6905
|
+
* declare const job: ExportJob;
|
|
6906
|
+
* // ---cut---
|
|
6907
|
+
* const answer = await job.cancel();
|
|
6908
|
+
* if (answer === "tooLate") await plugin.osd.show("Too late: the file is being written");
|
|
6909
|
+
* ```
|
|
6910
|
+
*
|
|
6911
|
+
* @since 1.0.0
|
|
6912
|
+
*/
|
|
6060
6913
|
cancel(): Promise<"requested" | "tooLate" | "terminal">;
|
|
6061
|
-
/**
|
|
6914
|
+
/**
|
|
6915
|
+
* Read the state from the player now.
|
|
6916
|
+
*
|
|
6917
|
+
* @capability media.read
|
|
6918
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6919
|
+
* `media.read`.
|
|
6920
|
+
*
|
|
6921
|
+
* @example Read a job's state now
|
|
6922
|
+
* The plugin reads the newest state of an export without waiting for an
|
|
6923
|
+
* event. `refresh` asks the player and resolves with the state, which
|
|
6924
|
+
* `state` then holds.
|
|
6925
|
+
* ```ts
|
|
6926
|
+
* declare const job: ExportJob;
|
|
6927
|
+
* // ---cut---
|
|
6928
|
+
* const state = await job.refresh();
|
|
6929
|
+
* console.log(state.status);
|
|
6930
|
+
* ```
|
|
6931
|
+
*
|
|
6932
|
+
* @since 1.0.0
|
|
6933
|
+
*/
|
|
6062
6934
|
refresh(): Promise<JobState>;
|
|
6063
6935
|
}
|
|
6064
6936
|
/**
|
|
@@ -6086,21 +6958,73 @@ export interface ExportJob extends Disposable {
|
|
|
6086
6958
|
* await snapshot.dispose();
|
|
6087
6959
|
* }
|
|
6088
6960
|
* ```
|
|
6961
|
+
*
|
|
6962
|
+
* @remarks
|
|
6963
|
+
* Exports run in the player's offline worker from a frozen
|
|
6964
|
+
* {@link Snapshot}: a still frame or a clip, raw or with this plugin's
|
|
6965
|
+
* effects (and the primary subtitle). Playback is not disturbed. A plugin
|
|
6966
|
+
* holds at most 4 snapshots at a time (16 in the player); release them
|
|
6967
|
+
* when done.
|
|
6968
|
+
*
|
|
6969
|
+
* @capability media.read and media.control (processed snapshots also
|
|
6970
|
+
* `playback.read` and `subtitles.read`)
|
|
6971
|
+
* @since 1.0.0
|
|
6972
|
+
* @category Media
|
|
6089
6973
|
*/
|
|
6090
6974
|
export interface MediaCapture {
|
|
6091
6975
|
/**
|
|
6092
6976
|
* Freeze the current source (and optionally this plugin set's effects)
|
|
6093
6977
|
* as an input for exports. Fails `stale` when the source changed since
|
|
6094
|
-
* `ifCurrent`; `unsupported` for
|
|
6978
|
+
* `ifCurrent`; `unsupported` for subtitles that cannot be flattened (only
|
|
6979
|
+
* plain SRT text can) or a picture over the size limits; `noSource` when
|
|
6980
|
+
* nothing is playing.
|
|
6981
|
+
*
|
|
6982
|
+
* @capability media.read and media.control
|
|
6983
|
+
* @throws `stale` (rejects) The source changed since `ifCurrent`.
|
|
6984
|
+
* @throws `noSource` (rejects) Nothing is playing.
|
|
6985
|
+
* @throws `invalid` (rejects) `at` at or after the end of the source.
|
|
6986
|
+
* @throws `busy` (rejects) This plugin holds 4 snapshots (or the player 16);
|
|
6987
|
+
* release one.
|
|
6988
|
+
* @throws `unavailable` (rejects) A processed snapshot while the picture is
|
|
6989
|
+
* still being prepared.
|
|
6990
|
+
* @throws `unsupported` (rejects) A processed snapshot the player cannot
|
|
6991
|
+
* freeze: subtitles that cannot be flattened (only plain SRT text can),
|
|
6992
|
+
* or a picture over the size limits.
|
|
6993
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6994
|
+
* `media.read` and `media.control`.
|
|
6995
|
+
*
|
|
6996
|
+
* @example Freeze the frame at the 10-second mark
|
|
6997
|
+
* The plugin freezes the frame at 0:10 of the open file, raw, to export it
|
|
6998
|
+
* later. `capture` resolves with a `Snapshot` once the player has frozen
|
|
6999
|
+
* that input; the snapshot stays valid when playback moves on or the file
|
|
7000
|
+
* changes, until it is released.
|
|
7001
|
+
* ```ts
|
|
7002
|
+
* const snapshot = await plugin.media.capture({ at: 10 }); // seconds
|
|
7003
|
+
* try {
|
|
7004
|
+
* // … export it …
|
|
7005
|
+
* } finally {
|
|
7006
|
+
* await snapshot.dispose(); // frees one of the plugin's 4 snapshot slots
|
|
7007
|
+
* }
|
|
7008
|
+
* ```
|
|
7009
|
+
*
|
|
7010
|
+
* @since 1.0.0
|
|
6095
7011
|
*/
|
|
6096
7012
|
capture(options?: CaptureOptions): Promise<Snapshot>;
|
|
6097
7013
|
}
|
|
6098
7014
|
/**
|
|
6099
7015
|
* Declaration of {@link Resolvers.add}: a web resolver
|
|
6100
7016
|
* ({@link WebResolverOptions}) or a file resolver ({@link FileResolverOptions}).
|
|
7017
|
+
*
|
|
7018
|
+
* @since 1.0.0
|
|
7019
|
+
* @category Sources
|
|
6101
7020
|
*/
|
|
6102
7021
|
export type ResolverOptions = WebResolverOptions | FileResolverOptions;
|
|
6103
|
-
/**
|
|
7022
|
+
/**
|
|
7023
|
+
* A resolver for web pages: `https://host/…` to playable stream URLs.
|
|
7024
|
+
*
|
|
7025
|
+
* @since 1.0.0
|
|
7026
|
+
* @category Sources
|
|
7027
|
+
*/
|
|
6104
7028
|
export interface WebResolverOptions {
|
|
6105
7029
|
/** Local id, unique within the plugin. */
|
|
6106
7030
|
id: string;
|
|
@@ -6165,6 +7089,9 @@ export interface WebResolverOptions {
|
|
|
6165
7089
|
* },
|
|
6166
7090
|
* });
|
|
6167
7091
|
* ```
|
|
7092
|
+
*
|
|
7093
|
+
* @since 1.5.3
|
|
7094
|
+
* @category Sources
|
|
6168
7095
|
*/
|
|
6169
7096
|
export interface FileResolverOptions {
|
|
6170
7097
|
/** Local id, unique within the plugin. */
|
|
@@ -6189,6 +7116,9 @@ export interface FileResolverOptions {
|
|
|
6189
7116
|
* failures "无法启动播放". An error thrown by `frame()` keeps its message.
|
|
6190
7117
|
* To give your own text for a file you can tell is unusable (a bad header),
|
|
6191
7118
|
* check it in `resolve` and throw there.
|
|
7119
|
+
*
|
|
7120
|
+
* @since 1.5.3
|
|
7121
|
+
* @category Sources
|
|
6192
7122
|
*/
|
|
6193
7123
|
export interface ResolvedStream {
|
|
6194
7124
|
/** Always `"stream"`. */
|
|
@@ -6207,7 +7137,38 @@ export interface ResolvedStream {
|
|
|
6207
7137
|
*/
|
|
6208
7138
|
started?(stream: Stream): void;
|
|
6209
7139
|
}
|
|
6210
|
-
/**
|
|
7140
|
+
/**
|
|
7141
|
+
* A registered resolver. Disposing it unregisters it and aborts resolutions in flight.
|
|
7142
|
+
*
|
|
7143
|
+
* @remarks
|
|
7144
|
+
* A registered resolver, from {@link Resolvers.add}. Disposing it, its
|
|
7145
|
+
* scope or stopping the plugin unregisters it and aborts resolutions in
|
|
7146
|
+
* flight; the extensions or hosts are free again for other plugins.
|
|
7147
|
+
*
|
|
7148
|
+
* @capability source.resolve
|
|
7149
|
+
*
|
|
7150
|
+
* @example Report when another plugin already handles the format
|
|
7151
|
+
* The plugin tells the user when its `.ch8` resolver could not register
|
|
7152
|
+
* because another plugin holds that extension. `ready` rejects with
|
|
7153
|
+
* `conflict`; for another plugin's claim that does not fail activation, so
|
|
7154
|
+
* the plugin keeps running without the resolver.
|
|
7155
|
+
* ```ts
|
|
7156
|
+
* const resolver = plugin.resolvers.add({
|
|
7157
|
+
* id: "chip8",
|
|
7158
|
+
* extensions: ["ch8"],
|
|
7159
|
+
* async resolve() {
|
|
7160
|
+
* throw plugin.i18n.error("Not ready yet.");
|
|
7161
|
+
* },
|
|
7162
|
+
* });
|
|
7163
|
+
* // Rejects with `conflict` when the extension is taken.
|
|
7164
|
+
* resolver.ready.catch((error: PluginError) => {
|
|
7165
|
+
* if (error.code === "conflict") void plugin.osd.show("Another plugin plays .ch8 files");
|
|
7166
|
+
* });
|
|
7167
|
+
* ```
|
|
7168
|
+
*
|
|
7169
|
+
* @since 1.0.0
|
|
7170
|
+
* @category Sources
|
|
7171
|
+
*/
|
|
6211
7172
|
export interface Resolver extends Disposable {
|
|
6212
7173
|
/**
|
|
6213
7174
|
* Resolves when the player registered it; rejects otherwise: `conflict`
|
|
@@ -6215,6 +7176,13 @@ export interface Resolver extends Disposable {
|
|
|
6215
7176
|
* plugin's: activation goes on; this plugin's own: activation fails),
|
|
6216
7177
|
* `invalid` for a malformed declaration (a player before 0.4.1 also for
|
|
6217
7178
|
* `extensions`).
|
|
7179
|
+
*
|
|
7180
|
+
* @throws `conflict` (rejects) Another resolver has the extension or the
|
|
7181
|
+
* scheme + host, or this plugin already registered this id.
|
|
7182
|
+
* @throws `busy` (rejects) The player already holds 16 resolvers.
|
|
7183
|
+
* @throws `invalid` (rejects) A malformed declaration.
|
|
7184
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7185
|
+
* `source.resolve`.
|
|
6218
7186
|
*/
|
|
6219
7187
|
readonly ready: Promise<void>;
|
|
6220
7188
|
}
|
|
@@ -6241,6 +7209,20 @@ export interface Resolver extends Disposable {
|
|
|
6241
7209
|
* },
|
|
6242
7210
|
* });
|
|
6243
7211
|
* ```
|
|
7212
|
+
*
|
|
7213
|
+
* @remarks
|
|
7214
|
+
* A resolver turns something the player cannot play by itself into
|
|
7215
|
+
* something it can: a web page address into stream URLs
|
|
7216
|
+
* ({@link WebResolverOptions}), or a local file of a format the plugin
|
|
7217
|
+
* understands into a stream the plugin generates
|
|
7218
|
+
* ({@link FileResolverOptions}). The player asks the resolver whenever
|
|
7219
|
+
* anyone opens a matching address or file (the Open dialog, the playlist,
|
|
7220
|
+
* another plugin), so the plugin does not intercept opens itself.
|
|
7221
|
+
*
|
|
7222
|
+
* @capability source.resolve (file resolvers also `producer.av`)
|
|
7223
|
+
* @see [Playback state, controls and sources](../../docs/concepts/playback.md)
|
|
7224
|
+
* @since 1.0.0
|
|
7225
|
+
* @category Sources
|
|
6244
7226
|
*/
|
|
6245
7227
|
export interface Resolvers {
|
|
6246
7228
|
/**
|
|
@@ -6249,6 +7231,29 @@ export interface Resolvers {
|
|
|
6249
7231
|
* replaced. One scheme + host pair, and one extension, has one resolver
|
|
6250
7232
|
* (`conflict` otherwise). A rejected `resolve` fails the open with the
|
|
6251
7233
|
* error's message.
|
|
7234
|
+
*
|
|
7235
|
+
* @capability source.resolve
|
|
7236
|
+
*
|
|
7237
|
+
* @example Play a site's pages through the plugin
|
|
7238
|
+
* Opening a `https://video.example.com/…` address plays the page's video.
|
|
7239
|
+
* `add` returns at once and registers in the background (`ready` reports
|
|
7240
|
+
* the answer); from then on the player calls `resolve` for every matching
|
|
7241
|
+
* open, and the error a rejected `resolve` carries is what the user sees.
|
|
7242
|
+
* ```ts
|
|
7243
|
+
* plugin.resolvers.add({
|
|
7244
|
+
* id: "example",
|
|
7245
|
+
* schemes: ["https"],
|
|
7246
|
+
* hosts: ["video.example.com"], // exact host names
|
|
7247
|
+
* async resolve(url, signal) {
|
|
7248
|
+
* const response = await fetch(url, { signal });
|
|
7249
|
+
* if (!response.ok) throw new Error(`the page answered ${response.status}`);
|
|
7250
|
+
* const page = (await response.json()) as { title: string; stream: string };
|
|
7251
|
+
* return { kind: "media", originalUrl: url, title: page.title, primary: { url: page.stream } };
|
|
7252
|
+
* },
|
|
7253
|
+
* });
|
|
7254
|
+
* ```
|
|
7255
|
+
*
|
|
7256
|
+
* @since 1.0.0
|
|
6252
7257
|
*/
|
|
6253
7258
|
add(options: ResolverOptions): Resolver;
|
|
6254
7259
|
/**
|
|
@@ -6304,6 +7309,9 @@ export interface Resolvers {
|
|
|
6304
7309
|
* The manifest of a package, as validated. The display fields and
|
|
6305
7310
|
* `minPlayerVersion` are optional (player 0.2.0); see the API reference for
|
|
6306
7311
|
* their rules.
|
|
7312
|
+
*
|
|
7313
|
+
* @since 1.0.0
|
|
7314
|
+
* @category Packages
|
|
6307
7315
|
*/
|
|
6308
7316
|
export interface PackageManifest {
|
|
6309
7317
|
/** Plugin id. */
|
|
@@ -6344,6 +7352,9 @@ export interface PackageManifest {
|
|
|
6344
7352
|
* upgrade the plugin and keep its data; links and local files count as one
|
|
6345
7353
|
* source. `bundled` marks the player's built-in plugins (set by the player
|
|
6346
7354
|
* only).
|
|
7355
|
+
*
|
|
7356
|
+
* @since 1.1.0
|
|
7357
|
+
* @category Packages
|
|
6347
7358
|
*/
|
|
6348
7359
|
export type PackageSource = {
|
|
6349
7360
|
/** A repository. */
|
|
@@ -6365,7 +7376,12 @@ export type PackageSource = {
|
|
|
6365
7376
|
/** Shipped with the player (built-in). */
|
|
6366
7377
|
kind: "bundled";
|
|
6367
7378
|
};
|
|
6368
|
-
/**
|
|
7379
|
+
/**
|
|
7380
|
+
* Options of {@link PackageCandidate.install}.
|
|
7381
|
+
*
|
|
7382
|
+
* @since 1.1.0
|
|
7383
|
+
* @category Packages
|
|
7384
|
+
*/
|
|
6369
7385
|
export interface InstallOptions {
|
|
6370
7386
|
/**
|
|
6371
7387
|
* Where the package comes from (default `{ kind: "file" }`). Repository
|
|
@@ -6380,7 +7396,34 @@ export interface InstallOptions {
|
|
|
6380
7396
|
*/
|
|
6381
7397
|
replace?: boolean;
|
|
6382
7398
|
}
|
|
6383
|
-
/**
|
|
7399
|
+
/**
|
|
7400
|
+
* A validated package directory, from {@link Packages.validate}.
|
|
7401
|
+
*
|
|
7402
|
+
* @remarks
|
|
7403
|
+
* A package the player has checked, from {@link Packages.validate} or
|
|
7404
|
+
* {@link Packages.validateArchive}: its manifest, digest and the checks
|
|
7405
|
+
* it passed. The player copies the whole package when it checks it and
|
|
7406
|
+
* installs that copy, so later changes to the directory do not matter.
|
|
7407
|
+
* Installing starts a {@link PackageChange}; the player keeps only the 32
|
|
7408
|
+
* most recent candidates.
|
|
7409
|
+
*
|
|
7410
|
+
* @capability plugins.manage
|
|
7411
|
+
*
|
|
7412
|
+
* @example Show what a package asks for before installing it
|
|
7413
|
+
* Before installing, the plugin shows the package's name and the
|
|
7414
|
+
* capabilities it declares. The candidate's `manifest` is the manifest
|
|
7415
|
+
* as the player read it.
|
|
7416
|
+
* ```ts
|
|
7417
|
+
* declare const directory: string;
|
|
7418
|
+
* // ---cut---
|
|
7419
|
+
* const candidate = await plugin.packages.validate(directory);
|
|
7420
|
+
* const { id, version, capabilities } = candidate.manifest;
|
|
7421
|
+
* await plugin.osd.show("{} {}: {}", [id, version, capabilities.join(", ")]);
|
|
7422
|
+
* ```
|
|
7423
|
+
*
|
|
7424
|
+
* @since 1.0.0
|
|
7425
|
+
* @category Packages
|
|
7426
|
+
*/
|
|
6384
7427
|
export interface PackageCandidate {
|
|
6385
7428
|
/** The package directory. */
|
|
6386
7429
|
readonly directory: string;
|
|
@@ -6397,28 +7440,138 @@ export interface PackageCandidate {
|
|
|
6397
7440
|
* player). A reserved id (`vestra` or `vestra.*`, which every built-in
|
|
6398
7441
|
* uses) fails with `reserved` (player 0.4.1; already when validated), as
|
|
6399
7442
|
* only the player installs built-in plugins.
|
|
7443
|
+
*
|
|
7444
|
+
* @capability plugins.manage
|
|
7445
|
+
* @throws `conflict` (rejects) The plugin is installed from another source
|
|
7446
|
+
* and `replace` is not set.
|
|
7447
|
+
* @throws `unsupported` (rejects) A built-in plugin's id.
|
|
7448
|
+
* @throws `reserved` (rejects) A reserved id (`vestra`, `vestra.*`).
|
|
7449
|
+
* @throws `busy` (rejects) The plugin library is being cleaned up, or the
|
|
7450
|
+
* player already keeps 64 change records.
|
|
7451
|
+
* @throws `revisionConflict` (rejects) The installed revision of this
|
|
7452
|
+
* plugin changed meanwhile; call `install` again (no new validation
|
|
7453
|
+
* needed).
|
|
7454
|
+
* @throws `stale` (rejects) The candidate is older than the 32 the player
|
|
7455
|
+
* keeps.
|
|
7456
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7457
|
+
* `plugins.manage`.
|
|
7458
|
+
*
|
|
7459
|
+
* @example Replace a plugin installed from another source
|
|
7460
|
+
* The new package replaces the installed plugin even though it came from
|
|
7461
|
+
* another source. Without `replace` the player refuses with `conflict`;
|
|
7462
|
+
* the same source simply upgrades and keeps the plugin's data.
|
|
7463
|
+
* ```ts
|
|
7464
|
+
* declare const candidate: PackageCandidate;
|
|
7465
|
+
* // ---cut---
|
|
7466
|
+
* const change = await candidate.install({ replace: true });
|
|
7467
|
+
* await change.done; // "committed" once the new plugin runs
|
|
7468
|
+
* ```
|
|
7469
|
+
*
|
|
7470
|
+
* @since 1.0.0
|
|
6400
7471
|
*/
|
|
6401
7472
|
install(options?: InstallOptions): Promise<PackageChange>;
|
|
6402
7473
|
}
|
|
6403
|
-
/**
|
|
7474
|
+
/**
|
|
7475
|
+
* The progress of an install or uninstall.
|
|
7476
|
+
*
|
|
7477
|
+
* @since 1.0.0
|
|
7478
|
+
* @category Packages
|
|
7479
|
+
*/
|
|
6404
7480
|
export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
|
|
6405
7481
|
/**
|
|
6406
7482
|
* An install or uninstall in progress. The SDK never submits a
|
|
6407
7483
|
* change twice: after a lost reply it only queries the same request.
|
|
7484
|
+
*
|
|
7485
|
+
* @remarks
|
|
7486
|
+
* An install or uninstall the player carries out in steps (`preparing`,
|
|
7487
|
+
* `activating`, possibly `restoring`) until a terminal status. The SDK
|
|
7488
|
+
* asks the player for the status about every 400 ms until then; `done`
|
|
7489
|
+
* resolves with the terminal status and does not reject for a failed
|
|
7490
|
+
* change: `message` says why.
|
|
7491
|
+
*
|
|
7492
|
+
* @capability plugins.manage
|
|
7493
|
+
*
|
|
7494
|
+
* @example Report each step of an install
|
|
7495
|
+
* The OSD shows each step of the install and the end result. `onChange`
|
|
7496
|
+
* runs for every new status; `done` resolves when the change is over.
|
|
7497
|
+
* ```ts
|
|
7498
|
+
* declare const change: PackageChange;
|
|
7499
|
+
* // ---cut---
|
|
7500
|
+
* change.onChange((status) => void plugin.osd.show("Install: {}", [status]));
|
|
7501
|
+
* const end = await change.done;
|
|
7502
|
+
* if (end !== "committed") await plugin.osd.show("Not installed: {}", [change.message ?? end]);
|
|
7503
|
+
* ```
|
|
7504
|
+
*
|
|
7505
|
+
* @since 1.0.0
|
|
7506
|
+
* @category Packages
|
|
6408
7507
|
*/
|
|
6409
7508
|
export interface PackageChange {
|
|
6410
7509
|
/** Current status. */
|
|
6411
7510
|
readonly status: PackageChangeStatus;
|
|
6412
7511
|
/** Details for failures, `null` otherwise. */
|
|
6413
7512
|
readonly message: string | null;
|
|
6414
|
-
/**
|
|
7513
|
+
/**
|
|
7514
|
+
* The status changed.
|
|
7515
|
+
*
|
|
7516
|
+
* @capability plugins.manage
|
|
7517
|
+
*
|
|
7518
|
+
* @example Close a progress window when the change ends
|
|
7519
|
+
* The plugin's progress window closes once the install is over, whatever
|
|
7520
|
+
* the outcome. The listener receives each new status; the terminal ones
|
|
7521
|
+
* are `committed`, `cancelled`, `failed`, `failedDegraded` and `tooLate`
|
|
7522
|
+
* (`tooLate` only answers a `cancel` of a change that had committed, and
|
|
7523
|
+
* `committed` may follow it).
|
|
7524
|
+
* ```ts
|
|
7525
|
+
* declare const change: PackageChange;
|
|
7526
|
+
* declare const progress: Window;
|
|
7527
|
+
* // ---cut---
|
|
7528
|
+
* change.onChange((status) => {
|
|
7529
|
+
* if (["committed", "cancelled", "failed", "failedDegraded", "tooLate"].includes(status))
|
|
7530
|
+
* void progress.close();
|
|
7531
|
+
* });
|
|
7532
|
+
* ```
|
|
7533
|
+
*
|
|
7534
|
+
* @since 1.0.0
|
|
7535
|
+
*/
|
|
6415
7536
|
onChange(listener: (status: PackageChangeStatus) => void): Disposable;
|
|
6416
|
-
/**
|
|
7537
|
+
/**
|
|
7538
|
+
* Resolves with the final status.
|
|
7539
|
+
*
|
|
7540
|
+
* @throws `missing` (rejects) The player no longer knows the change (a
|
|
7541
|
+
* `HostError` with that code); a failed change is a terminal status,
|
|
7542
|
+
* not a rejection.
|
|
7543
|
+
*/
|
|
6417
7544
|
readonly done: Promise<PackageChangeStatus>;
|
|
6418
|
-
/**
|
|
7545
|
+
/**
|
|
7546
|
+
* Ask to cancel (it may be `tooLate`).
|
|
7547
|
+
*
|
|
7548
|
+
* @capability plugins.manage
|
|
7549
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7550
|
+
* `plugins.manage`.
|
|
7551
|
+
*
|
|
7552
|
+
* @example Cancel an install the user no longer wants
|
|
7553
|
+
* The install stops if it has not gone too far. `cancel` asks the player;
|
|
7554
|
+
* the change then ends `cancelled`. When the change had already committed
|
|
7555
|
+
* the player answers `tooLate` and `done` still resolves `committed`; a
|
|
7556
|
+
* change that is restoring cannot be cancelled and ends `failed` or
|
|
7557
|
+
* `failedDegraded`.
|
|
7558
|
+
* ```ts
|
|
7559
|
+
* declare const change: PackageChange;
|
|
7560
|
+
* // ---cut---
|
|
7561
|
+
* await change.cancel();
|
|
7562
|
+
* console.log(await change.done); // "cancelled"; "committed" when it was too late
|
|
7563
|
+
* ```
|
|
7564
|
+
*
|
|
7565
|
+
* @since 1.0.0
|
|
7566
|
+
*/
|
|
6419
7567
|
cancel(): Promise<void>;
|
|
6420
7568
|
}
|
|
6421
|
-
/**
|
|
7569
|
+
/**
|
|
7570
|
+
* One plugin as the player knows it (`packages.list`).
|
|
7571
|
+
*
|
|
7572
|
+
* @since 1.0.0
|
|
7573
|
+
* @category Packages
|
|
7574
|
+
*/
|
|
6422
7575
|
export interface PluginInfo {
|
|
6423
7576
|
/** Manifest id. */
|
|
6424
7577
|
id: string;
|
|
@@ -6477,6 +7630,8 @@ export interface PluginInfo {
|
|
|
6477
7630
|
* `description` and `tags` as the player shows them.
|
|
6478
7631
|
*
|
|
6479
7632
|
* @since 1.5.9
|
|
7633
|
+
*
|
|
7634
|
+
* @category Packages
|
|
6480
7635
|
*/
|
|
6481
7636
|
export interface PluginDisplay {
|
|
6482
7637
|
/** `name`, translated; `null` when the manifest has none. */
|
|
@@ -6492,6 +7647,8 @@ export interface PluginDisplay {
|
|
|
6492
7647
|
* One refusal in {@link PluginInfo.warnings}.
|
|
6493
7648
|
*
|
|
6494
7649
|
* @since 1.5.2
|
|
7650
|
+
*
|
|
7651
|
+
* @category Packages
|
|
6495
7652
|
*/
|
|
6496
7653
|
export interface PluginWarning {
|
|
6497
7654
|
/**
|
|
@@ -6528,6 +7685,17 @@ export interface PluginWarning {
|
|
|
6528
7685
|
* // `message` says why a change did not commit.
|
|
6529
7686
|
* if ((await change.done) !== "committed") throw new Error(change.message ?? "install failed");
|
|
6530
7687
|
* ```
|
|
7688
|
+
*
|
|
7689
|
+
* @remarks
|
|
7690
|
+
* The plugins the player knows: installed, built-in and this run's
|
|
7691
|
+
* development loads. Reading the list needs no capability; installing,
|
|
7692
|
+
* enabling, reloading and uninstalling need `plugins.manage` and act on
|
|
7693
|
+
* other plugins, never on the calling one. Built-in plugins update with
|
|
7694
|
+
* the player and cannot be installed, disabled or uninstalled.
|
|
7695
|
+
*
|
|
7696
|
+
* @capability None for `list` and `onChange`; plugins.manage for the rest.
|
|
7697
|
+
* @since 1.0.0
|
|
7698
|
+
* @category Packages
|
|
6531
7699
|
*/
|
|
6532
7700
|
export interface Packages {
|
|
6533
7701
|
/**
|
|
@@ -6538,6 +7706,37 @@ export interface Packages {
|
|
|
6538
7706
|
* The player keeps the 32 most recent candidates of all plugins; an
|
|
6539
7707
|
* older one's `install` fails with `stale` (validate again). Before
|
|
6540
7708
|
* player 0.4.2 the 33rd validation of a run failed with `busy` instead.
|
|
7709
|
+
*
|
|
7710
|
+
* @capability plugins.manage
|
|
7711
|
+
* @throws `host` (rejects) Not a package directory, or a manifest that
|
|
7712
|
+
* breaks the rules (another `apiVersion` included); `message` says why.
|
|
7713
|
+
* @throws `invalid` (rejects) A bad request (a path that is not a string).
|
|
7714
|
+
* @throws `javascript` (rejects) The entry module does not parse.
|
|
7715
|
+
* @throws `slint` (rejects) A Slint file does not compile.
|
|
7716
|
+
* @throws `reserved` (rejects) A reserved id (`vestra`, `vestra.*`).
|
|
7717
|
+
* @throws `versionMismatch` (rejects) The package's `minPlayerVersion` is
|
|
7718
|
+
* newer than the player.
|
|
7719
|
+
* @throws `disabled` (rejects) The calling plugin is stopping.
|
|
7720
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7721
|
+
* `plugins.manage`.
|
|
7722
|
+
*
|
|
7723
|
+
* @example Check a package directory before installing it
|
|
7724
|
+
* The plugin checks a folder it unpacked and installs it if the player
|
|
7725
|
+
* accepts it. `validate` runs the player's checks and resolves with a
|
|
7726
|
+
* candidate; a package that fails them rejects with the reason.
|
|
7727
|
+
* ```ts
|
|
7728
|
+
* declare const directory: string;
|
|
7729
|
+
* // ---cut---
|
|
7730
|
+
* try {
|
|
7731
|
+
* // Rejects with the reason when the package fails the checks.
|
|
7732
|
+
* const candidate = await plugin.packages.validate(directory);
|
|
7733
|
+
* await (await candidate.install()).done;
|
|
7734
|
+
* } catch (error) {
|
|
7735
|
+
* await plugin.osd.show("Not a valid plugin: {}", [(error as Error).message]);
|
|
7736
|
+
* }
|
|
7737
|
+
* ```
|
|
7738
|
+
*
|
|
7739
|
+
* @since 1.0.0
|
|
6541
7740
|
*/
|
|
6542
7741
|
validate(directory: string): Promise<PackageCandidate>;
|
|
6543
7742
|
/**
|
|
@@ -6545,19 +7744,97 @@ export interface Packages {
|
|
|
6545
7744
|
* same checks as {@link Packages.validate} after unpacking. Check the
|
|
6546
7745
|
* file's sha256 yourself before calling when you know it. A reserved id
|
|
6547
7746
|
* fails with `reserved`, as in {@link Packages.validate}.
|
|
7747
|
+
*
|
|
7748
|
+
* @capability plugins.manage
|
|
7749
|
+
* @throws `invalid` (rejects) The path does not end in `.vxp`, or the
|
|
7750
|
+
* archive cannot be unpacked (over 64 MB, too many files, a damaged zip,
|
|
7751
|
+
* an unsafe path).
|
|
7752
|
+
* @throws `host` (rejects) The unpacked package or its manifest breaks the
|
|
7753
|
+
* rules; `message` says why.
|
|
7754
|
+
* @throws `javascript` (rejects) The entry module does not parse.
|
|
7755
|
+
* @throws `slint` (rejects) A Slint file does not compile.
|
|
7756
|
+
* @throws `reserved` (rejects) A reserved id.
|
|
7757
|
+
* @throws `versionMismatch` (rejects) The package's `minPlayerVersion` is
|
|
7758
|
+
* newer than the player.
|
|
7759
|
+
* @throws `disabled` (rejects) The calling plugin is stopping.
|
|
7760
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7761
|
+
* `plugins.manage`.
|
|
7762
|
+
*
|
|
7763
|
+
* @example Install a downloaded package file
|
|
7764
|
+
* A `.vxp` the plugin downloaded is checked and installed. The player
|
|
7765
|
+
* unpacks and checks it like a directory; checking the file's sha256
|
|
7766
|
+
* first, when the store publishes one, is up to the plugin.
|
|
7767
|
+
* ```ts
|
|
7768
|
+
* declare const file: string;
|
|
7769
|
+
* // ---cut---
|
|
7770
|
+
* const candidate = await plugin.packages.validateArchive(file); // a .vxp path
|
|
7771
|
+
* const change = await candidate.install();
|
|
7772
|
+
* await plugin.osd.show("Install: {}", [await change.done]);
|
|
7773
|
+
* ```
|
|
7774
|
+
*
|
|
7775
|
+
* @since 1.1.0
|
|
6548
7776
|
*/
|
|
6549
7777
|
validateArchive(file: string): Promise<PackageCandidate>;
|
|
6550
|
-
/**
|
|
7778
|
+
/**
|
|
7779
|
+
* Every plugin: running, installed, and this run's development loads. No capability.
|
|
7780
|
+
*
|
|
7781
|
+
* @capability None.
|
|
7782
|
+
* @throws `Error` (rejects) Only when the player cannot be asked; no
|
|
7783
|
+
* capability is needed.
|
|
7784
|
+
*
|
|
7785
|
+
* @example Show which plugins are running
|
|
7786
|
+
* The plugin lists the running plugins by name. `list` asks the player
|
|
7787
|
+
* and resolves with every plugin it knows, with its state, origin and
|
|
7788
|
+
* display texts in the interface language.
|
|
7789
|
+
* ```ts
|
|
7790
|
+
* const plugins = await plugin.packages.list();
|
|
7791
|
+
* const running = plugins.filter((info) => info.state === "running");
|
|
7792
|
+
* console.log(running.map((info) => info.display?.name ?? info.id));
|
|
7793
|
+
* ```
|
|
7794
|
+
*
|
|
7795
|
+
* @since 1.0.0
|
|
7796
|
+
*/
|
|
6551
7797
|
list(): Promise<PluginInfo[]>;
|
|
6552
|
-
/**
|
|
7798
|
+
/**
|
|
7799
|
+
* The new list after any start, stop, failure, install or uninstall. No capability.
|
|
7800
|
+
*
|
|
7801
|
+
* @capability None.
|
|
7802
|
+
*
|
|
7803
|
+
* @example Refresh a plugin list after any change
|
|
7804
|
+
* A store window's list updates when a plugin starts, stops, fails, is
|
|
7805
|
+
* installed or uninstalled. The listener receives the new list each time.
|
|
7806
|
+
* ```ts
|
|
7807
|
+
* plugin.packages.onChange((plugins) => console.log(plugins.length, "plugins"));
|
|
7808
|
+
* ```
|
|
7809
|
+
*
|
|
7810
|
+
* @since 1.0.0
|
|
7811
|
+
*/
|
|
6553
7812
|
onChange(listener: (plugins: PluginInfo[]) => void): Disposable;
|
|
6554
7813
|
/**
|
|
6555
7814
|
* Stop or start a plugin (`plugins.manage`). An installed plugin stays
|
|
6556
7815
|
* disabled across launches; a development load only for this run.
|
|
6557
7816
|
* `invalid` to disable oneself, `missing` unknown.
|
|
6558
|
-
* `busy` while it is being installed. `unsupported` to disable a built-in
|
|
7817
|
+
* `busy` while it is being installed or reloaded. `unsupported` to disable a built-in
|
|
6559
7818
|
* ({@link PluginInfo.builtin}): users manage third-party plugins only
|
|
6560
7819
|
* (players released with SDK 1.5.18 on; older ones disabled built-ins too).
|
|
7820
|
+
*
|
|
7821
|
+
* @capability plugins.manage
|
|
7822
|
+
* @throws `invalid` (rejects) Disabling the calling plugin itself.
|
|
7823
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7824
|
+
* @throws `busy` (rejects) The plugin is being installed or reloaded.
|
|
7825
|
+
* @throws `unsupported` (rejects) A built-in plugin.
|
|
7826
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7827
|
+
* `plugins.manage`.
|
|
7828
|
+
*
|
|
7829
|
+
* @example Turn a third-party plugin off
|
|
7830
|
+
* The plugin stops another plugin and keeps it disabled across launches
|
|
7831
|
+
* until it is enabled again. Built-in plugins cannot be disabled
|
|
7832
|
+
* (`unsupported`).
|
|
7833
|
+
* ```ts
|
|
7834
|
+
* await plugin.packages.setEnabled("someone.demo", false);
|
|
7835
|
+
* ```
|
|
7836
|
+
*
|
|
7837
|
+
* @since 1.0.0
|
|
6561
7838
|
*/
|
|
6562
7839
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
6563
7840
|
/**
|
|
@@ -6578,6 +7855,23 @@ export interface Packages {
|
|
|
6578
7855
|
* @player 0.5.1; older players reject it as an unknown request
|
|
6579
7856
|
* (`unsupported`).
|
|
6580
7857
|
* @since 1.5.19
|
|
7858
|
+
*
|
|
7859
|
+
* @capability plugins.manage
|
|
7860
|
+
* @throws `invalid` (rejects) The calling plugin itself, or a disabled
|
|
7861
|
+
* plugin.
|
|
7862
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7863
|
+
* @throws `busy` (rejects) The plugin is being installed or reloaded.
|
|
7864
|
+
* @throws `unsupported` (rejects) A player before 0.5.1.
|
|
7865
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7866
|
+
* `plugins.manage`.
|
|
7867
|
+
*
|
|
7868
|
+
* @example Restart a plugin that misbehaves
|
|
7869
|
+
* The other plugin is stopped and started again from the same package and
|
|
7870
|
+
* data. `reload` resolves once the old process is stopped and the start is
|
|
7871
|
+
* scheduled; `onChange` then shows `starting` and `running` (or `failed`).
|
|
7872
|
+
* ```ts
|
|
7873
|
+
* await plugin.packages.reload("someone.demo");
|
|
7874
|
+
* ```
|
|
6581
7875
|
*/
|
|
6582
7876
|
reload(id: string): Promise<void>;
|
|
6583
7877
|
/**
|
|
@@ -6586,19 +7880,51 @@ export interface Packages {
|
|
|
6586
7880
|
* reconnects to its data. Its user bindings stay (as orphans).
|
|
6587
7881
|
* `unsupported` for built-ins and development loads, `missing`
|
|
6588
7882
|
* unknown, `invalid` for oneself.
|
|
7883
|
+
*
|
|
7884
|
+
* @capability plugins.manage
|
|
7885
|
+
* @throws `unsupported` (rejects) A built-in plugin or a development load.
|
|
7886
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7887
|
+
* @throws `invalid` (rejects) The calling plugin itself.
|
|
7888
|
+
* @throws `busy` (rejects) The plugin library is being cleaned up, or that
|
|
7889
|
+
* plugin is being installed or reloaded.
|
|
7890
|
+
* @throws `revisionConflict` (rejects) The installed revision of that
|
|
7891
|
+
* plugin changed meanwhile; call `uninstall` again.
|
|
7892
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7893
|
+
* `plugins.manage`.
|
|
7894
|
+
*
|
|
7895
|
+
* @example Uninstall a plugin together with its data
|
|
7896
|
+
* The other plugin is removed and its data directory deleted, so a later
|
|
7897
|
+
* reinstall starts fresh. Without `removeData` a reinstall from the same
|
|
7898
|
+
* source finds its data again.
|
|
7899
|
+
* ```ts
|
|
7900
|
+
* const change = await plugin.packages.uninstall("someone.demo", { removeData: true });
|
|
7901
|
+
* await change.done;
|
|
7902
|
+
* ```
|
|
7903
|
+
*
|
|
7904
|
+
* @since 1.0.0
|
|
6589
7905
|
*/
|
|
6590
7906
|
uninstall(id: string, options?: {
|
|
6591
7907
|
removeData?: boolean;
|
|
6592
7908
|
}): Promise<PackageChange>;
|
|
6593
7909
|
}
|
|
6594
|
-
/**
|
|
7910
|
+
/**
|
|
7911
|
+
* A command: manifest id (`"host"` for the player's own) and command id.
|
|
7912
|
+
*
|
|
7913
|
+
* @since 1.0.0
|
|
7914
|
+
* @category Commands
|
|
7915
|
+
*/
|
|
6595
7916
|
export interface BindingRef {
|
|
6596
7917
|
/** Manifest id, or `"host"`. */
|
|
6597
7918
|
plugin: string;
|
|
6598
7919
|
/** Command id within that plugin. */
|
|
6599
7920
|
command: string;
|
|
6600
7921
|
}
|
|
6601
|
-
/**
|
|
7922
|
+
/**
|
|
7923
|
+
* One command's bindings, from {@link Bindings.list}.
|
|
7924
|
+
*
|
|
7925
|
+
* @since 1.0.0
|
|
7926
|
+
* @category Commands
|
|
7927
|
+
*/
|
|
6602
7928
|
export interface BindingInfo extends BindingRef {
|
|
6603
7929
|
/** Menu title. */
|
|
6604
7930
|
title: string;
|
|
@@ -6648,9 +7974,40 @@ export interface BindingInfo extends BindingRef {
|
|
|
6648
7974
|
* // later
|
|
6649
7975
|
* await plugin.bindings.reset({ plugin: "host", command: "mute" });
|
|
6650
7976
|
* ```
|
|
7977
|
+
*
|
|
7978
|
+
* @remarks
|
|
7979
|
+
* The player's binding table: every command's default keys (from
|
|
7980
|
+
* {@link CommandOptions.bindings}, the player's own as `host/<action>`)
|
|
7981
|
+
* and the user's overrides on top, saved across launches. A key-settings
|
|
7982
|
+
* plugin lists the table, writes overrides and resets them; the player
|
|
7983
|
+
* applies the change at once to every running plugin.
|
|
7984
|
+
*
|
|
7985
|
+
* @capability bindings.manage
|
|
7986
|
+
* @see [Commands, menus and key bindings](../../docs/concepts/commands.md)
|
|
7987
|
+
* @since 1.0.0
|
|
7988
|
+
* @category Commands
|
|
6651
7989
|
*/
|
|
6652
7990
|
export interface Bindings {
|
|
6653
|
-
/**
|
|
7991
|
+
/**
|
|
7992
|
+
* Every declared command, and overrides whose command is gone.
|
|
7993
|
+
*
|
|
7994
|
+
* @capability bindings.manage
|
|
7995
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7996
|
+
* `bindings.manage`.
|
|
7997
|
+
*
|
|
7998
|
+
* @example Find the commands that use a key
|
|
7999
|
+
* A key-settings window shows which commands Ctrl+K is bound to now. `list`
|
|
8000
|
+
* resolves with every declared command and its current bindings (the
|
|
8001
|
+
* defaults with the user's overrides applied) and with the overrides
|
|
8002
|
+
* whose command is not running (`orphans`).
|
|
8003
|
+
* ```ts
|
|
8004
|
+
* const { commands } = await plugin.bindings.list();
|
|
8005
|
+
* const users = commands.filter((command) => command.current.includes("Ctrl+K"));
|
|
8006
|
+
* console.log(users.map((command) => `${command.plugin}/${command.command}`)); // "host" for the player
|
|
8007
|
+
* ```
|
|
8008
|
+
*
|
|
8009
|
+
* @since 1.0.0
|
|
8010
|
+
*/
|
|
6654
8011
|
list(): Promise<{
|
|
6655
8012
|
/** Every declared command (running plugins and the player). */
|
|
6656
8013
|
commands: BindingInfo[];
|
|
@@ -6667,11 +8024,65 @@ export interface Bindings {
|
|
|
6667
8024
|
* `invalid` for a bad binding, `missing` for an unknown command that has
|
|
6668
8025
|
* no override either.
|
|
6669
8026
|
* At most 16 inputs, syntax as {@link CommandOptions.bindings}.
|
|
8027
|
+
*
|
|
8028
|
+
* @capability bindings.manage
|
|
8029
|
+
* @throws `invalid` (rejects) A malformed binding, or more than 16.
|
|
8030
|
+
* @throws `missing` (rejects) An unknown command that has no override
|
|
8031
|
+
* either.
|
|
8032
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
8033
|
+
* `bindings.manage`.
|
|
8034
|
+
*
|
|
8035
|
+
* @example Give the player's Mute command a second key
|
|
8036
|
+
* Mute now answers to M and to Ctrl+M. `set` replaces the command's
|
|
8037
|
+
* bindings with the list given (an override saved across launches), so it
|
|
8038
|
+
* repeats the default it keeps; `[]` would unbind it.
|
|
8039
|
+
* ```ts
|
|
8040
|
+
* await plugin.bindings.set({ plugin: "host", command: "mute" }, ["M", "Ctrl+M"]);
|
|
8041
|
+
* ```
|
|
8042
|
+
*
|
|
8043
|
+
* @since 1.0.0
|
|
6670
8044
|
*/
|
|
6671
8045
|
set(ref: BindingRef, inputs: string[]): Promise<void>;
|
|
6672
|
-
/**
|
|
8046
|
+
/**
|
|
8047
|
+
* Back to the defaults: one command, or every override when omitted.
|
|
8048
|
+
*
|
|
8049
|
+
* @capability bindings.manage
|
|
8050
|
+
* @throws `missing` (rejects) A command that has no override and is not
|
|
8051
|
+
* declared.
|
|
8052
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
8053
|
+
* `bindings.manage`.
|
|
8054
|
+
*
|
|
8055
|
+
* @example Restore every default key
|
|
8056
|
+
* A "Restore defaults" button removes all of the user's overrides, so
|
|
8057
|
+
* every command has its declared keys again. Without an argument `reset`
|
|
8058
|
+
* clears every override; with a reference only that command's.
|
|
8059
|
+
* ```ts
|
|
8060
|
+
* await plugin.bindings.reset(); // every override
|
|
8061
|
+
* ```
|
|
8062
|
+
*
|
|
8063
|
+
* @since 1.0.0
|
|
8064
|
+
*/
|
|
6673
8065
|
reset(ref?: BindingRef): Promise<void>;
|
|
6674
|
-
/**
|
|
8066
|
+
/**
|
|
8067
|
+
* Declarations or overrides changed (read `list()` again). At most every 200 ms.
|
|
8068
|
+
*
|
|
8069
|
+
* @capability bindings.manage
|
|
8070
|
+
*
|
|
8071
|
+
* @example Refresh a key list when bindings change
|
|
8072
|
+
* The plugin's key list stays current when the user changes a key in the
|
|
8073
|
+
* player or another plugin starts. The listener carries no data and runs
|
|
8074
|
+
* at most every 200 ms; the plugin reads the table again with `list`.
|
|
8075
|
+
* ```ts
|
|
8076
|
+
* declare function render(commands: BindingInfo[]): void;
|
|
8077
|
+
* // ---cut---
|
|
8078
|
+
* plugin.bindings.onChange(async () => {
|
|
8079
|
+
* const { commands } = await plugin.bindings.list(); // read the new table
|
|
8080
|
+
* render(commands);
|
|
8081
|
+
* });
|
|
8082
|
+
* ```
|
|
8083
|
+
*
|
|
8084
|
+
* @since 1.0.0
|
|
8085
|
+
*/
|
|
6675
8086
|
onChange(listener: () => void): Disposable;
|
|
6676
8087
|
}
|
|
6677
8088
|
/**
|