@vestra-player/sdk 1.5.31 → 1.5.34
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 +23 -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/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -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/subtitles.d.ts +59 -0
- package/dist/plugin/subtitles.js +70 -4
- package/dist/plugin/testing.d.ts +538 -12
- package/dist/plugin/testing.js +458 -23
- package/dist/plugin/types.d.ts +1524 -64
- 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 +16 -1
- package/dist/wire.js +10 -0
- package/llms-full.txt +1489 -55
- 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,48 @@ 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
|
-
* The operating system and the
|
|
3604
|
-
* and go
|
|
3994
|
+
* The operating system, the displays and the renderer, read when called
|
|
3995
|
+
* (displays come and go; the renderer can fall back from GPU to software
|
|
3996
|
+
* once). No capability. CPU and memory: Node's `os` module.
|
|
3997
|
+
*
|
|
3998
|
+
* @capability None.
|
|
3999
|
+
* @throws `busy` (rejects) The player could not start the request (no
|
|
4000
|
+
* thread available).
|
|
4001
|
+
*
|
|
4002
|
+
* @example Choose a size for the primary display
|
|
4003
|
+
* The plugin sizes its picture for the primary display. `system()` asks
|
|
4004
|
+
* the player; `displays` lists the displays present now, the primary one
|
|
4005
|
+
* first (empty without a user interface).
|
|
4006
|
+
* ```ts
|
|
4007
|
+
* const { displays } = await plugin.app.system();
|
|
4008
|
+
* const primary = displays[0];
|
|
4009
|
+
* if (primary) console.log(primary.width, primary.height);
|
|
4010
|
+
* ```
|
|
4011
|
+
*
|
|
4012
|
+
* @since 1.0.0
|
|
3605
4013
|
*/
|
|
3606
4014
|
system(): Promise<SystemInfo>;
|
|
3607
4015
|
}
|
|
3608
|
-
/**
|
|
4016
|
+
/**
|
|
4017
|
+
* What {@link App.system} reports.
|
|
4018
|
+
*
|
|
4019
|
+
* @see {@link App.system}
|
|
4020
|
+
* @since 1.0.0
|
|
4021
|
+
* @category Plugin
|
|
4022
|
+
*/
|
|
3609
4023
|
export interface SystemInfo {
|
|
3610
4024
|
/** The operating system family. */
|
|
3611
4025
|
readonly os: "macos" | "windows" | "linux" | "other";
|
|
@@ -3621,8 +4035,53 @@ export interface SystemInfo {
|
|
|
3621
4035
|
readonly arch: string;
|
|
3622
4036
|
/** Primary display first; empty without a user interface. */
|
|
3623
4037
|
readonly displays: readonly DisplayInfo[];
|
|
4038
|
+
/**
|
|
4039
|
+
* Which renderer draws the video now: `"gpu"` (the GPU renderer, so
|
|
4040
|
+
* {@link RenderOptions | render options} apply) or `"software"` (they
|
|
4041
|
+
* fail with `unsupported`). Read at each call. It changes at most once
|
|
4042
|
+
* while the player runs, from `"gpu"` to `"software"` when a window
|
|
4043
|
+
* cannot show GPU frames; the owner of the render options hears that
|
|
4044
|
+
* switch through {@link RenderOptions.onStatus}.
|
|
4045
|
+
*
|
|
4046
|
+
* Absent (`undefined`) on players before 0.5.12, which do not report it:
|
|
4047
|
+
* the renderer is then unknown, not software.
|
|
4048
|
+
*
|
|
4049
|
+
* Today the player renders on the GPU on macOS and in software on
|
|
4050
|
+
* Windows, Linux and without a user interface (see
|
|
4051
|
+
* [Platforms](../../docs/reference/platforms.md)); test this field
|
|
4052
|
+
* rather than {@link SystemInfo.os}.
|
|
4053
|
+
*
|
|
4054
|
+
* @example Offer the HDR menu only where render options work
|
|
4055
|
+
* The plugin adds its HDR command only when the player renders on the
|
|
4056
|
+
* GPU, instead of creating the render options just to find out from a
|
|
4057
|
+
* failed status. `system()` asks the player, which reports the renderer
|
|
4058
|
+
* drawing at that moment; no capability is needed to ask. A player before
|
|
4059
|
+
* 0.5.12 leaves the field out, so the test is for `"gpu"` exactly (an
|
|
4060
|
+
* older player gets no HDR command).
|
|
4061
|
+
* ```ts
|
|
4062
|
+
* const { rendering } = await plugin.app.system();
|
|
4063
|
+
* if (rendering === "gpu") {
|
|
4064
|
+
* const render = plugin.video.render(); // one handle, kept
|
|
4065
|
+
* plugin.commands.add({
|
|
4066
|
+
* id: "hdr",
|
|
4067
|
+
* path: ["@video", "HDR Tone Mapping"],
|
|
4068
|
+
* run: () => render.set({ toneMapping: "bt.2390", targetPeak: 250 }),
|
|
4069
|
+
* });
|
|
4070
|
+
* }
|
|
4071
|
+
* ```
|
|
4072
|
+
*
|
|
4073
|
+
* @player 0.5.12; older players leave it out (declare
|
|
4074
|
+
* `"minPlayerVersion": "0.5.12"` when the plugin relies on it).
|
|
4075
|
+
* @since 1.5.34
|
|
4076
|
+
*/
|
|
4077
|
+
readonly rendering?: "gpu" | "software";
|
|
3624
4078
|
}
|
|
3625
|
-
/**
|
|
4079
|
+
/**
|
|
4080
|
+
* One display in {@link SystemInfo.displays}.
|
|
4081
|
+
*
|
|
4082
|
+
* @since 1.0.0
|
|
4083
|
+
* @category Plugin
|
|
4084
|
+
*/
|
|
3626
4085
|
export interface DisplayInfo {
|
|
3627
4086
|
/** The system's name for it, `null` when it has none. */
|
|
3628
4087
|
readonly name: string | null;
|
|
@@ -5630,6 +6089,9 @@ export interface Osd {
|
|
|
5630
6089
|
* The state of a filter, display layer or render options handle.
|
|
5631
6090
|
* After `failed` nothing retries automatically until the next `set()`,
|
|
5632
6091
|
* `retry()` or a new source.
|
|
6092
|
+
*
|
|
6093
|
+
* @since 1.0.0
|
|
6094
|
+
* @category Processing
|
|
5633
6095
|
*/
|
|
5634
6096
|
export type FilterStatus =
|
|
5635
6097
|
/** No ready media; the value is kept and applied when media is ready. */
|
|
@@ -5705,6 +6167,18 @@ interface Filter<T> extends Disposable {
|
|
|
5705
6167
|
* picture.onStatus((s) => s.state === "failed" && plugin.osd.show(s.error.message));
|
|
5706
6168
|
* picture.set([{ kind: "eq", brightness: 0.1, contrast: 1.1, saturation: 1, gamma: 1 }]);
|
|
5707
6169
|
* ```
|
|
6170
|
+
*
|
|
6171
|
+
* @remarks
|
|
6172
|
+
* Members: `set(nodes)` records the desired chain (last write wins) and
|
|
6173
|
+
* sends it, one request at a time and only the newest; `status` and
|
|
6174
|
+
* `onStatus` report `waiting`, `applying`, `applied` or `failed`; `retry`
|
|
6175
|
+
* applies the current value again; `dispose` withdraws the chain. `set`
|
|
6176
|
+
* never throws: a refused chain shows as `failed` in `status`.
|
|
6177
|
+
*
|
|
6178
|
+
* @capability processing.video, playback.read and events
|
|
6179
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6180
|
+
* @since 1.0.0
|
|
6181
|
+
* @category Processing
|
|
5708
6182
|
*/
|
|
5709
6183
|
export type VideoFilter = Filter<VideoNode[]>;
|
|
5710
6184
|
/**
|
|
@@ -5724,6 +6198,16 @@ export type VideoFilter = Filter<VideoNode[]>;
|
|
|
5724
6198
|
* const audio = plugin.audio.filter("sound");
|
|
5725
6199
|
* audio.set({ nodes: [{ kind: "volume", gainDb: -6 }], delayMs: 120 }); // 10 ms steps
|
|
5726
6200
|
* ```
|
|
6201
|
+
*
|
|
6202
|
+
* @remarks
|
|
6203
|
+
* The same members and rules as {@link VideoFilter}: `set` records the
|
|
6204
|
+
* desired value and never throws, `status` and `onStatus` report the
|
|
6205
|
+
* result, `retry` applies it again, `dispose` withdraws it.
|
|
6206
|
+
*
|
|
6207
|
+
* @capability processing.audio, playback.read and events
|
|
6208
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6209
|
+
* @since 1.0.0
|
|
6210
|
+
* @category Processing
|
|
5727
6211
|
*/
|
|
5728
6212
|
export type AudioFilter = Filter<{
|
|
5729
6213
|
/** The audio chain; `[]` for none. */
|
|
@@ -5734,6 +6218,9 @@ export type AudioFilter = Filter<{
|
|
|
5734
6218
|
/**
|
|
5735
6219
|
* How the video sits in the video area; not part of snapshots or exports.
|
|
5736
6220
|
* Defaults: `{ aspect: null, fit: "keep", zoom: 1, panX: 0, panY: 0 }`.
|
|
6221
|
+
*
|
|
6222
|
+
* @since 1.0.0
|
|
6223
|
+
* @category Processing
|
|
5737
6224
|
*/
|
|
5738
6225
|
export interface ViewSettings {
|
|
5739
6226
|
/** Display aspect override 0.25–4; `null` = as the source. */
|
|
@@ -5761,24 +6248,73 @@ export interface ViewSettings {
|
|
|
5761
6248
|
* The video fills the video area (cropped instead of letterboxed), then
|
|
5762
6249
|
* zooms to 150 % shifted a little to the right. The first plugin to call
|
|
5763
6250
|
* `view()` owns the display layer; each `set` merges into its settings,
|
|
5764
|
-
* which stay across sources until the plugin
|
|
6251
|
+
* which stay across sources until the plugin disposes the view or stops.
|
|
5765
6252
|
* ```ts
|
|
5766
6253
|
* const view = plugin.video.view();
|
|
5767
6254
|
* view.set({ fit: "fill" });
|
|
5768
6255
|
* view.set({ zoom: 1.5, panX: 0.1 }); // merged with the previous write
|
|
5769
6256
|
* ```
|
|
6257
|
+
*
|
|
6258
|
+
* @remarks
|
|
6259
|
+
* Not part of snapshots or exports: it changes how the picture sits in the
|
|
6260
|
+
* video area, not the picture. `set` merges into the current settings and
|
|
6261
|
+
* never throws: an out-of-range value or unknown field shows as `failed`
|
|
6262
|
+
* (`invalid`) in `status` and is reported to `onError`, without sending
|
|
6263
|
+
* anything.
|
|
6264
|
+
*
|
|
6265
|
+
* @capability processing.video, playback.read and events (like
|
|
6266
|
+
* {@link VideoFilter})
|
|
6267
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6268
|
+
* @since 1.0.0
|
|
6269
|
+
* @category Processing
|
|
5770
6270
|
*/
|
|
5771
6271
|
export interface VideoView extends Disposable {
|
|
5772
|
-
/**
|
|
6272
|
+
/**
|
|
6273
|
+
* Merged into the current settings; last write wins.
|
|
6274
|
+
*
|
|
6275
|
+
* @capability processing.video
|
|
6276
|
+
*
|
|
6277
|
+
* @example Stretch a 4:3 video to fill the window
|
|
6278
|
+
* A 4:3 video fills a 16:9 window, distorted, as the user asked. Each
|
|
6279
|
+
* `set` merges the given fields into this plugin's view settings; the
|
|
6280
|
+
* newest write wins, and the settings stay for later files until the
|
|
6281
|
+
* plugin disposes the view or stops.
|
|
6282
|
+
* ```ts
|
|
6283
|
+
* plugin.video.view().set({ fit: "stretch" });
|
|
6284
|
+
* ```
|
|
6285
|
+
*
|
|
6286
|
+
* @since 1.0.0
|
|
6287
|
+
*/
|
|
5773
6288
|
set(value: Partial<ViewSettings>): void;
|
|
5774
6289
|
/** Current status (the same four states as filters). */
|
|
5775
6290
|
readonly status: FilterStatus;
|
|
5776
|
-
/**
|
|
6291
|
+
/**
|
|
6292
|
+
* The status changed.
|
|
6293
|
+
*
|
|
6294
|
+
* @capability processing.video
|
|
6295
|
+
*
|
|
6296
|
+
* @example Tell the user another plugin owns the view
|
|
6297
|
+
* When another plugin already controls the display layer, the OSD says
|
|
6298
|
+
* so. The first plugin to call `view()` owns it; this plugin's handle
|
|
6299
|
+
* then reports `failed` with `conflict`, and tries again on its next `set`.
|
|
6300
|
+
* ```ts
|
|
6301
|
+
* const view = plugin.video.view();
|
|
6302
|
+
* view.onStatus((status) => {
|
|
6303
|
+
* if (status.state === "failed" && status.error.code === "conflict")
|
|
6304
|
+
* void plugin.osd.show("Another plugin controls the picture size");
|
|
6305
|
+
* });
|
|
6306
|
+
* ```
|
|
6307
|
+
*
|
|
6308
|
+
* @since 1.0.0
|
|
6309
|
+
*/
|
|
5777
6310
|
onStatus(listener: (status: FilterStatus) => void): Disposable;
|
|
5778
6311
|
}
|
|
5779
6312
|
/**
|
|
5780
6313
|
* GPU renderer options; not part of snapshots or exports.
|
|
5781
6314
|
* Defaults: `{ toneMapping: "auto", targetPeak: 203, peakDetect: true, shaders: [], interpolation: false }`.
|
|
6315
|
+
*
|
|
6316
|
+
* @since 1.0.0
|
|
6317
|
+
* @category Processing
|
|
5782
6318
|
*/
|
|
5783
6319
|
export interface RenderSettings {
|
|
5784
6320
|
/** HDR → SDR: "auto" maps HDR sources only; "off" clips without a curve. */
|
|
@@ -5799,8 +6335,10 @@ export interface RenderSettings {
|
|
|
5799
6335
|
*
|
|
5800
6336
|
* Platforms: only macOS renders on the GPU. Under software rendering
|
|
5801
6337
|
* (Windows, Linux, headless, fallback) every `set` fails with
|
|
5802
|
-
* `unsupported
|
|
5803
|
-
*
|
|
6338
|
+
* `unsupported`; {@link SystemInfo.rendering} tells which renderer draws
|
|
6339
|
+
* without creating the handle. macOS OpenGL 4.1 has no
|
|
6340
|
+
* compute shaders, so `peakDetect` is accepted but has no effect; output
|
|
6341
|
+
* is always SDR.
|
|
5804
6342
|
*
|
|
5805
6343
|
* @example Tone-map HDR video and hide the menu where it cannot work
|
|
5806
6344
|
* On a GPU-rendering player HDR video is tone-mapped with BT.2390 for a
|
|
@@ -5817,19 +6355,69 @@ export interface RenderSettings {
|
|
|
5817
6355
|
* });
|
|
5818
6356
|
* render.set({ toneMapping: "bt.2390", targetPeak: 250 }); // nits
|
|
5819
6357
|
* ```
|
|
6358
|
+
*
|
|
6359
|
+
* @remarks
|
|
6360
|
+
* GPU renderer options (tone mapping, peak detection, shaders,
|
|
6361
|
+
* interpolation). Only the GPU renderer has them, which today means macOS;
|
|
6362
|
+
* under software rendering the handle is `failed` with `unsupported` after
|
|
6363
|
+
* the player's first answer to `render()` (reported to `onError` too), and so is
|
|
6364
|
+
* every `set`. Single owner, like {@link VideoView}. `set` never throws; the
|
|
6365
|
+
* result shows in `status`.
|
|
6366
|
+
*
|
|
6367
|
+
* @capability processing.video, playback.read and events (like
|
|
6368
|
+
* {@link VideoFilter})
|
|
6369
|
+
* @see [Video and audio processing](../../docs/concepts/processing.md)
|
|
6370
|
+
* @since 1.0.0
|
|
6371
|
+
* @category Processing
|
|
5820
6372
|
*/
|
|
5821
6373
|
export interface RenderOptions extends Disposable {
|
|
5822
|
-
/** Merged into the current settings; last write wins. */
|
|
5823
|
-
set(value: Partial<RenderSettings>): void;
|
|
5824
6374
|
/**
|
|
5825
|
-
*
|
|
5826
|
-
*
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
6375
|
+
* Merged into the current settings; last write wins.
|
|
6376
|
+
*
|
|
6377
|
+
* @capability processing.video
|
|
6378
|
+
*
|
|
6379
|
+
* @example Turn on motion interpolation
|
|
6380
|
+
* Motion becomes smoother on the GPU renderer. `set` merges the field into
|
|
6381
|
+
* the plugin's render options; under software rendering the status turns
|
|
6382
|
+
* `failed` with `unsupported` instead.
|
|
6383
|
+
* ```ts
|
|
6384
|
+
* plugin.video.render().set({ interpolation: true });
|
|
6385
|
+
* ```
|
|
6386
|
+
*
|
|
6387
|
+
* @since 1.0.0
|
|
6388
|
+
*/
|
|
6389
|
+
set(value: Partial<RenderSettings>): void;
|
|
6390
|
+
/**
|
|
6391
|
+
* `failed` with `unsupported` while the player renders in software;
|
|
6392
|
+
* `failed` with `failed` when mpv cannot compile a shader (pushed later).
|
|
6393
|
+
*/
|
|
6394
|
+
readonly status: FilterStatus;
|
|
6395
|
+
/**
|
|
6396
|
+
* The status changed (including later pushes such as shader compile errors).
|
|
6397
|
+
*
|
|
6398
|
+
* @capability processing.video
|
|
6399
|
+
*
|
|
6400
|
+
* @example Report a shader that does not compile
|
|
6401
|
+
* The OSD shows mpv's message when a shader the plugin set fails to
|
|
6402
|
+
* compile. The player pushes that failure after the `set` was accepted, so
|
|
6403
|
+
* it arrives through `onStatus`, not as a rejected call.
|
|
6404
|
+
* ```ts
|
|
6405
|
+
* const render = plugin.video.render();
|
|
6406
|
+
* render.onStatus((status) => {
|
|
6407
|
+
* if (status.state === "failed") void plugin.osd.show("Render: {}", [status.error.message]);
|
|
6408
|
+
* });
|
|
6409
|
+
* ```
|
|
6410
|
+
*
|
|
6411
|
+
* @since 1.0.0
|
|
6412
|
+
*/
|
|
6413
|
+
onStatus(listener: (status: FilterStatus) => void): Disposable;
|
|
6414
|
+
}
|
|
6415
|
+
/**
|
|
6416
|
+
* A subtitle track of the current source.
|
|
6417
|
+
*
|
|
6418
|
+
* @since 1.0.0
|
|
6419
|
+
* @category Subtitles
|
|
6420
|
+
*/
|
|
5833
6421
|
export interface SubtitleTrack {
|
|
5834
6422
|
/** Stable while the track exists; compare tracks by id. */
|
|
5835
6423
|
id: string;
|
|
@@ -5857,7 +6445,13 @@ export interface SubtitleTrack {
|
|
|
5857
6445
|
*/
|
|
5858
6446
|
streamIndex: number | null;
|
|
5859
6447
|
}
|
|
5860
|
-
/**
|
|
6448
|
+
/**
|
|
6449
|
+
* The subtitle state of the current source.
|
|
6450
|
+
*
|
|
6451
|
+
* @see {@link Subtitles.state}
|
|
6452
|
+
* @since 1.0.0
|
|
6453
|
+
* @category Subtitles
|
|
6454
|
+
*/
|
|
5861
6455
|
export interface SubtitlesState {
|
|
5862
6456
|
/** The source it belongs to ({@link PlayerState.sourceId}). */
|
|
5863
6457
|
readonly sourceId: number;
|
|
@@ -5888,7 +6482,12 @@ export interface SubtitlesState {
|
|
|
5888
6482
|
/** The last error, `null` without one. */
|
|
5889
6483
|
readonly error: string | null;
|
|
5890
6484
|
}
|
|
5891
|
-
/**
|
|
6485
|
+
/**
|
|
6486
|
+
* Subtitle calls act on the selection you see; pass `ifCurrent` to pin an older one.
|
|
6487
|
+
*
|
|
6488
|
+
* @since 1.0.0
|
|
6489
|
+
* @category Subtitles
|
|
6490
|
+
*/
|
|
5892
6491
|
export interface SubtitleConditional {
|
|
5893
6492
|
/** The subtitle state the call is conditional on; `stale` otherwise. */
|
|
5894
6493
|
ifCurrent?: SubtitlesState;
|
|
@@ -5922,18 +6521,104 @@ export interface SubtitleConditional {
|
|
|
5922
6521
|
* offsetMs: 0,
|
|
5923
6522
|
* });
|
|
5924
6523
|
* ```
|
|
6524
|
+
*
|
|
6525
|
+
* @remarks
|
|
6526
|
+
* The subtitle tracks of the current source, the primary and secondary
|
|
6527
|
+
* selection, uploaded files and the style. `state` is the SDK's snapshot of
|
|
6528
|
+
* the player's subtitle state (`null` until first read). The player has no
|
|
6529
|
+
* subtitle event: the SDK reads the state again when the source, its phase
|
|
6530
|
+
* or the backend changes (with `events`) and after this plugin's own
|
|
6531
|
+
* changes, so a selection the user or another plugin changed shows only
|
|
6532
|
+
* after `refresh()`. A change without `ifCurrent` reads the state first
|
|
6533
|
+
* and applies to it; pass `ifCurrent` to make it fail with `stale` when the
|
|
6534
|
+
* selection changed since that state. Each change is one operation the
|
|
6535
|
+
* player confirms; `select`, `remove` and `setStyle` resolve with the new
|
|
6536
|
+
* state, `load` with the new track.
|
|
6537
|
+
*
|
|
6538
|
+
* @capability `subtitles.read` to read; `subtitles.control` and
|
|
6539
|
+
* `subtitles.read` to change; `events` to re-read on source changes.
|
|
6540
|
+
*
|
|
6541
|
+
* @see [Load subtitles from a plugin](../../docs/how-to/subtitles.md)
|
|
6542
|
+
* @since 1.0.0
|
|
6543
|
+
* @category Subtitles
|
|
5925
6544
|
*/
|
|
5926
6545
|
export interface Subtitles {
|
|
5927
6546
|
/** `null` until first read. */
|
|
5928
6547
|
readonly state: SubtitlesState | null;
|
|
5929
|
-
/**
|
|
6548
|
+
/**
|
|
6549
|
+
* The subtitle state changed.
|
|
6550
|
+
*
|
|
6551
|
+
* @capability events and subtitles.read
|
|
6552
|
+
*
|
|
6553
|
+
* @example Show the subtitle language after a new file opens
|
|
6554
|
+
* When a new file starts, the OSD names the language of its primary
|
|
6555
|
+
* subtitle. The SDK reads the subtitle state again when the source or its
|
|
6556
|
+
* phase changes (there is no subtitle event, so a selection changed in the
|
|
6557
|
+
* player shows only after `refresh()`) and then calls the listener;
|
|
6558
|
+
* `selected` is `null` when subtitles are off.
|
|
6559
|
+
* ```ts
|
|
6560
|
+
* plugin.subtitles.onChange((state) => {
|
|
6561
|
+
* // A track's language is null when the file does not say.
|
|
6562
|
+
* void plugin.osd.show("Subtitles: {}", [state.selected?.language ?? "off"]);
|
|
6563
|
+
* });
|
|
6564
|
+
* ```
|
|
6565
|
+
*
|
|
6566
|
+
* @since 1.0.0
|
|
6567
|
+
*/
|
|
5930
6568
|
onChange(listener: (state: SubtitlesState) => void): Disposable;
|
|
5931
|
-
/**
|
|
6569
|
+
/**
|
|
6570
|
+
* Read the state from the player now.
|
|
6571
|
+
*
|
|
6572
|
+
* @capability subtitles.read
|
|
6573
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6574
|
+
* `subtitles.read`.
|
|
6575
|
+
*
|
|
6576
|
+
* @example Count the subtitle tracks of the file
|
|
6577
|
+
* The plugin reads how many subtitle tracks the file has before building a
|
|
6578
|
+
* menu. `refresh` asks the player and resolves with the current state, also
|
|
6579
|
+
* without `events`; `state` holds it afterwards.
|
|
6580
|
+
* ```ts
|
|
6581
|
+
* const { tracks } = await plugin.subtitles.refresh();
|
|
6582
|
+
* await plugin.osd.show("{} subtitle tracks", [tracks.length]);
|
|
6583
|
+
* ```
|
|
6584
|
+
*
|
|
6585
|
+
* @since 1.0.0
|
|
6586
|
+
*/
|
|
5932
6587
|
refresh(): Promise<SubtitlesState>;
|
|
5933
6588
|
/**
|
|
5934
6589
|
* `role: "secondary"` sets the secondary subtitle: a text track that is not
|
|
5935
6590
|
* the primary one (`invalid` otherwise). A new source clears it.
|
|
5936
6591
|
* `track: null` turns the role off.
|
|
6592
|
+
*
|
|
6593
|
+
* @capability subtitles.control and subtitles.read
|
|
6594
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source or the subtitle
|
|
6595
|
+
* selection changed since that state.
|
|
6596
|
+
* @throws `invalid` (rejects) An unknown track; as secondary, a bitmap
|
|
6597
|
+
* track or the primary one; as primary, the current secondary.
|
|
6598
|
+
* @throws `missing` (rejects) The track is not in the current source.
|
|
6599
|
+
* @throws `unavailable` (rejects) Nothing loaded, or the source is still
|
|
6600
|
+
* loading or not yet confirmed.
|
|
6601
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6602
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6603
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6604
|
+
*
|
|
6605
|
+
* @example Turn on the first English subtitle
|
|
6606
|
+
* The first English track becomes the primary subtitle, but only in the
|
|
6607
|
+
* file the plugin looked at. Embedded tracks are chosen by number, so
|
|
6608
|
+
* without `ifCurrent` a select after another file opened would pick that
|
|
6609
|
+
* file's track with the same number; with `ifCurrent: state` it fails with
|
|
6610
|
+
* `stale` instead. Without `ifCurrent`, `select` reads the newest state
|
|
6611
|
+
* itself and almost never fails with `stale`.
|
|
6612
|
+
* ```ts
|
|
6613
|
+
* const state = await plugin.subtitles.refresh();
|
|
6614
|
+
* const english = state.tracks.find((track) => track.language === "en");
|
|
6615
|
+
* if (english)
|
|
6616
|
+
* await plugin.subtitles.select(english, { ifCurrent: state }).catch((error: PluginError) => {
|
|
6617
|
+
* if (error.code !== "stale") throw error; // another file or selection now
|
|
6618
|
+
* });
|
|
6619
|
+
* ```
|
|
6620
|
+
*
|
|
6621
|
+
* @since 1.0.0
|
|
5937
6622
|
*/
|
|
5938
6623
|
select(track: SubtitleTrack | null, options?: SubtitleConditional & {
|
|
5939
6624
|
role?: "primary" | "secondary";
|
|
@@ -5949,7 +6634,43 @@ export interface Subtitles {
|
|
|
5949
6634
|
*
|
|
5950
6635
|
* Limits: SRT ≤ 2 MiB and ≤ 10,000 cues; ASS ≤ 2 MiB with `[Script Info]`
|
|
5951
6636
|
* and `[Events]` and 1–20,000 `Dialogue:` lines. Convert other formats
|
|
5952
|
-
* (VTT, SAMI) to SRT first.
|
|
6637
|
+
* (VTT, SAMI) to SRT first. Per source, a plugin has at most 4 loaded
|
|
6638
|
+
* tracks at once and loads at most 16 files (removed ones count), and
|
|
6639
|
+
* every plugin together at most 16 tracks; see `SUBTITLE_LIMITS` in
|
|
6640
|
+
* `@vestra-player/sdk/testing`.
|
|
6641
|
+
*
|
|
6642
|
+
* @capability subtitles.control and subtitles.read
|
|
6643
|
+
* @throws `invalid` (rejects) A bad `format` or `title`, or a file over the
|
|
6644
|
+
* limits.
|
|
6645
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source changed since
|
|
6646
|
+
* that state.
|
|
6647
|
+
* @throws `unavailable` (rejects) Nothing loaded, or the source is still
|
|
6648
|
+
* loading.
|
|
6649
|
+
* @throws `aborted` (rejects) `signal` aborted before the commit (an
|
|
6650
|
+
* `AbortError`); the upload is discarded.
|
|
6651
|
+
* @throws `busy` (rejects) Too many subtitle operations at once, or a
|
|
6652
|
+
* limit of `SUBTITLE_LIMITS` is reached (4 tracks of this plugin, 16
|
|
6653
|
+
* loads of this plugin or 16 tracks in the source, or another of its
|
|
6654
|
+
* limits); nothing is loaded.
|
|
6655
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6656
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6657
|
+
*
|
|
6658
|
+
* @example Load a downloaded SRT and select it
|
|
6659
|
+
* A subtitle file the plugin downloaded becomes a track of the current file
|
|
6660
|
+
* and is selected. The plugin passes the file's bytes; the player parses
|
|
6661
|
+
* them, adds an `external` track for this source only and resolves with
|
|
6662
|
+
* it once the upload is committed.
|
|
6663
|
+
* ```ts
|
|
6664
|
+
* declare const url: string;
|
|
6665
|
+
* // ---cut---
|
|
6666
|
+
* const response = await fetch(url);
|
|
6667
|
+
* if (!response.ok) throw new Error(`subtitles: HTTP ${response.status}`);
|
|
6668
|
+
* const bytes = new Uint8Array(await response.arrayBuffer());
|
|
6669
|
+
* const track = await plugin.subtitles.load(bytes, { title: "Downloaded" }); // selected
|
|
6670
|
+
* await plugin.osd.show("Loaded {}", [track.title]);
|
|
6671
|
+
* ```
|
|
6672
|
+
*
|
|
6673
|
+
* @since 1.0.0
|
|
5953
6674
|
*/
|
|
5954
6675
|
load(bytes: Uint8Array, options?: SubtitleConditional & {
|
|
5955
6676
|
format?: "srt" | "ass";
|
|
@@ -5957,19 +6678,69 @@ export interface Subtitles {
|
|
|
5957
6678
|
select?: "primary" | "secondary" | "none";
|
|
5958
6679
|
signal?: AbortSignal;
|
|
5959
6680
|
}): Promise<SubtitleTrack>;
|
|
5960
|
-
/**
|
|
6681
|
+
/**
|
|
6682
|
+
* Remove a track this plugin loaded.
|
|
6683
|
+
*
|
|
6684
|
+
* @capability subtitles.control and subtitles.read
|
|
6685
|
+
* @throws `invalid` (rejects) The track was not loaded by this plugin.
|
|
6686
|
+
* @throws `stale` (rejects) With `ifCurrent`: the source changed since
|
|
6687
|
+
* that state.
|
|
6688
|
+
* @throws `unavailable` (rejects) Nothing is loaded, or the source is
|
|
6689
|
+
* still loading.
|
|
6690
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6691
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6692
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6693
|
+
*
|
|
6694
|
+
* @example Remove the subtitle the plugin loaded
|
|
6695
|
+
* The plugin's downloaded subtitle disappears from the track list. A plugin
|
|
6696
|
+
* can remove only tracks it loaded itself; a new source removes them
|
|
6697
|
+
* anyway.
|
|
6698
|
+
* ```ts
|
|
6699
|
+
* declare const loaded: SubtitleTrack;
|
|
6700
|
+
* // ---cut---
|
|
6701
|
+
* await plugin.subtitles.remove(loaded);
|
|
6702
|
+
* ```
|
|
6703
|
+
*
|
|
6704
|
+
* @since 1.0.0
|
|
6705
|
+
*/
|
|
5961
6706
|
remove(track: SubtitleTrack, options?: SubtitleConditional): Promise<SubtitlesState>;
|
|
5962
6707
|
/**
|
|
5963
6708
|
* Set this plugin's style layer; `null` removes it. The newest call of
|
|
5964
6709
|
* this plugin replaces its earlier layer. When several plugins have a
|
|
5965
6710
|
* layer, the player uses the one whose manifest id sorts last, whatever
|
|
5966
6711
|
* the order of the calls.
|
|
6712
|
+
*
|
|
6713
|
+
* @capability subtitles.control and subtitles.read
|
|
6714
|
+
* @throws `invalid` (rejects) A field out of range.
|
|
6715
|
+
* @throws `unsupported` (rejects) A font that is not in `state.fonts`, or a
|
|
6716
|
+
* primary subtitle that is a bitmap track.
|
|
6717
|
+
* @throws `stale` (rejects) With `ifCurrent`: the subtitle state changed
|
|
6718
|
+
* since that state.
|
|
6719
|
+
* @throws `unavailable` (rejects) Nothing is loaded, or the source is
|
|
6720
|
+
* still loading.
|
|
6721
|
+
* @throws `busy` (rejects) Too many subtitle operations at once.
|
|
6722
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6723
|
+
* `subtitles.control` and `subtitles.read`.
|
|
6724
|
+
*
|
|
6725
|
+
* @example Take the plugin's style off again
|
|
6726
|
+
* The plugin removes its style layer, so the subtitles go back to the
|
|
6727
|
+
* player's default (or to another plugin's layer). `null` removes only this
|
|
6728
|
+
* plugin's layer; the effective layer is the one whose manifest id sorts
|
|
6729
|
+
* last among the plugins that still have one.
|
|
6730
|
+
* ```ts
|
|
6731
|
+
* await plugin.subtitles.setStyle(null);
|
|
6732
|
+
* ```
|
|
6733
|
+
*
|
|
6734
|
+
* @since 1.0.0
|
|
5967
6735
|
*/
|
|
5968
6736
|
setStyle(style: SubtitleStyle | null, options?: SubtitleConditional): Promise<SubtitlesState>;
|
|
5969
6737
|
}
|
|
5970
6738
|
/**
|
|
5971
6739
|
* Options of {@link MediaCapture.capture}. Conditional on `player.state`
|
|
5972
6740
|
* like other controls.
|
|
6741
|
+
*
|
|
6742
|
+
* @since 1.0.0
|
|
6743
|
+
* @category Media
|
|
5973
6744
|
*/
|
|
5974
6745
|
export interface CaptureOptions extends Conditional {
|
|
5975
6746
|
/** Source time; defaults to the current position at the moment of capture. */
|
|
@@ -5981,7 +6752,36 @@ export interface CaptureOptions extends Conditional {
|
|
|
5981
6752
|
/** Audio to freeze with the snapshot: the selected track, none, or a track id. */
|
|
5982
6753
|
audio?: "selected" | "none" | number;
|
|
5983
6754
|
}
|
|
5984
|
-
/**
|
|
6755
|
+
/**
|
|
6756
|
+
* A frozen, immutable input for exports. Dispose to release it.
|
|
6757
|
+
*
|
|
6758
|
+
* @remarks
|
|
6759
|
+
* A frozen input, from {@link MediaCapture.capture}: the source, the time
|
|
6760
|
+
* and (processed) the effects and subtitle as they were. Exports read it in
|
|
6761
|
+
* the player's offline worker, so one snapshot can be exported several
|
|
6762
|
+
* times and playback is unaffected. Disposing it releases the player's
|
|
6763
|
+
* copy; an export using it after that fails with `released`.
|
|
6764
|
+
*
|
|
6765
|
+
* @capability media.read and media.control
|
|
6766
|
+
*
|
|
6767
|
+
* @example Export one frame as PNG and as JPEG
|
|
6768
|
+
* The same frozen frame is written twice, lossless and compressed. Each
|
|
6769
|
+
* `export` starts a job of its own; the snapshot is released after both
|
|
6770
|
+
* are done.
|
|
6771
|
+
* ```ts
|
|
6772
|
+
* const snapshot = await plugin.media.capture();
|
|
6773
|
+
* try {
|
|
6774
|
+
* const png = await snapshot.export({ format: "png", destination: "/tmp/frame.png" });
|
|
6775
|
+
* const jpeg = await snapshot.export({ format: "jpeg", destination: "/tmp/frame.jpg", quality: 85 });
|
|
6776
|
+
* await Promise.all([png.done, jpeg.done]); // terminal states, not errors
|
|
6777
|
+
* } finally {
|
|
6778
|
+
* await snapshot.dispose();
|
|
6779
|
+
* }
|
|
6780
|
+
* ```
|
|
6781
|
+
*
|
|
6782
|
+
* @since 1.0.0
|
|
6783
|
+
* @category Media
|
|
6784
|
+
*/
|
|
5985
6785
|
export interface Snapshot extends Disposable {
|
|
5986
6786
|
/** Without or with effects. */
|
|
5987
6787
|
readonly kind: "raw" | "processed";
|
|
@@ -6005,6 +6805,32 @@ export interface Snapshot extends Disposable {
|
|
|
6005
6805
|
/**
|
|
6006
6806
|
* Start exporting; the job runs in the host's offline worker.
|
|
6007
6807
|
* One snapshot can be exported several times.
|
|
6808
|
+
*
|
|
6809
|
+
* @capability media.read and media.control
|
|
6810
|
+
* @throws `released` (rejects) The snapshot was already released.
|
|
6811
|
+
* @throws `invalid` (rejects) Bad options (`quality` outside 1–100, a
|
|
6812
|
+
* relative destination, a clip `end` not after the snapshot time or
|
|
6813
|
+
* past the duration).
|
|
6814
|
+
* @throws `busy` (rejects) This plugin runs 2 exports (or the player 8).
|
|
6815
|
+
* @throws `unavailable` (rejects) No FFmpeg, or the export worker could not
|
|
6816
|
+
* start.
|
|
6817
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6818
|
+
* `media.read` and `media.control`.
|
|
6819
|
+
*
|
|
6820
|
+
* @example Export a 30-second clip
|
|
6821
|
+
* A clip from the snapshot time to 30 seconds later is written as MP4.
|
|
6822
|
+
* `export` resolves once the job is accepted; the job then runs in the
|
|
6823
|
+
* player's offline worker, and `done` resolves with its terminal state,
|
|
6824
|
+
* whose `outcome` has the destination or the error.
|
|
6825
|
+
* ```ts
|
|
6826
|
+
* declare const snapshot: Snapshot;
|
|
6827
|
+
* // ---cut---
|
|
6828
|
+
* const job = await snapshot.export({ format: "mp4", destination: "/tmp/clip.mp4", end: snapshot.time + 30 });
|
|
6829
|
+
* const end = await job.done;
|
|
6830
|
+
* if (end.status !== "completed") await plugin.osd.show("Export {}", [end.status]);
|
|
6831
|
+
* ```
|
|
6832
|
+
*
|
|
6833
|
+
* @since 1.0.0
|
|
6008
6834
|
*/
|
|
6009
6835
|
export(options: ExportOptions): Promise<ExportJob>;
|
|
6010
6836
|
}
|
|
@@ -6016,6 +6842,9 @@ export interface Snapshot extends Disposable {
|
|
|
6016
6842
|
* choose the format. `sampleTime` picks the frame (default: the snapshot
|
|
6017
6843
|
* time); `end` is the clip's end in source seconds. On Windows the export
|
|
6018
6844
|
* worker needs FFmpeg 6.1 or newer.
|
|
6845
|
+
*
|
|
6846
|
+
* @since 1.0.0
|
|
6847
|
+
* @category Media
|
|
6019
6848
|
*/
|
|
6020
6849
|
export type ExportOptions = {
|
|
6021
6850
|
/** A lossless frame. */
|
|
@@ -6046,19 +6875,111 @@ export type ExportOptions = {
|
|
|
6046
6875
|
* `completed`, `failed` or `cancelled`; once terminal, `outcome` is
|
|
6047
6876
|
* `{ destination }`, `{ error: { code, message } }` or `{ cancelled: true }`.
|
|
6048
6877
|
* `facts` describes what the worker produced (versions, range, streams).
|
|
6878
|
+
*
|
|
6879
|
+
* @since 1.0.0
|
|
6880
|
+
* @category Media
|
|
6049
6881
|
*/
|
|
6050
6882
|
export type JobState = Omit<MediaJob, "jobId">;
|
|
6051
|
-
/**
|
|
6883
|
+
/**
|
|
6884
|
+
* An export, from {@link Snapshot.export}. Disposing an unfinished job cancels it.
|
|
6885
|
+
*
|
|
6886
|
+
* @remarks
|
|
6887
|
+
* One export, from {@link Snapshot.export}. `state` is the SDK's last copy
|
|
6888
|
+
* of the job's state: the SDK polls the player about every 350 ms until the
|
|
6889
|
+
* job ends, and `refresh` reads it at once. `done` resolves
|
|
6890
|
+
* once the job is terminal (`completed`, `failed` or `cancelled`) and never
|
|
6891
|
+
* rejects for a failed export: the failure is in `outcome`. Disposing an
|
|
6892
|
+
* unfinished job cancels it.
|
|
6893
|
+
*
|
|
6894
|
+
* @capability media.read and media.control
|
|
6895
|
+
*
|
|
6896
|
+
* @example Show export progress and cancel on request
|
|
6897
|
+
* The OSD shows the job's status while it runs, and a Cancel command stops
|
|
6898
|
+
* it. `onChange` follows the state (`queued`, `running`, `cancelling`,
|
|
6899
|
+
* then a terminal one); `cancel` asks the worker to stop. After
|
|
6900
|
+
* `requested` the job usually ends `cancelled`, or `failed` when the
|
|
6901
|
+
* cleanup failed.
|
|
6902
|
+
* ```ts
|
|
6903
|
+
* declare const job: ExportJob;
|
|
6904
|
+
* // ---cut---
|
|
6905
|
+
* job.onChange((state) => void plugin.osd.show("Export: {}", [state.status]));
|
|
6906
|
+
* plugin.commands.add({ id: "cancel-export", title: "Cancel export", run: () => job.cancel() });
|
|
6907
|
+
* await job.done; // completed, failed or cancelled
|
|
6908
|
+
* ```
|
|
6909
|
+
*
|
|
6910
|
+
* @since 1.0.0
|
|
6911
|
+
* @category Media
|
|
6912
|
+
*/
|
|
6052
6913
|
export interface ExportJob extends Disposable {
|
|
6053
6914
|
/** Latest state. */
|
|
6054
6915
|
readonly state: JobState;
|
|
6055
|
-
/**
|
|
6916
|
+
/**
|
|
6917
|
+
* The state changed.
|
|
6918
|
+
*
|
|
6919
|
+
* @capability media.read
|
|
6920
|
+
*
|
|
6921
|
+
* @example Log each state of an export
|
|
6922
|
+
* The plugin's log records each status the job goes through (`queued`,
|
|
6923
|
+
* `running`, `cancelling` after a cancel, then a terminal one). The listener runs when the SDK learns a
|
|
6924
|
+
* new state from its polling.
|
|
6925
|
+
* ```ts
|
|
6926
|
+
* declare const job: ExportJob;
|
|
6927
|
+
* // ---cut---
|
|
6928
|
+
* job.onChange((state) => console.log("export", state.status));
|
|
6929
|
+
* ```
|
|
6930
|
+
*
|
|
6931
|
+
* @since 1.0.0
|
|
6932
|
+
*/
|
|
6056
6933
|
onChange(listener: (state: JobState) => void): Disposable;
|
|
6057
|
-
/**
|
|
6934
|
+
/**
|
|
6935
|
+
* Resolves with the terminal state (completed, failed or cancelled).
|
|
6936
|
+
*
|
|
6937
|
+
* @throws `disposed` (rejects) The job handle was disposed while waiting.
|
|
6938
|
+
*/
|
|
6058
6939
|
readonly done: Promise<JobState>;
|
|
6059
|
-
/**
|
|
6940
|
+
/**
|
|
6941
|
+
* Ask to cancel: `requested`, `tooLate` (already publishing) or `terminal` (already over).
|
|
6942
|
+
*
|
|
6943
|
+
* @capability media.control
|
|
6944
|
+
* @throws `rejected` (rejects) The player did not accept the request.
|
|
6945
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6946
|
+
* `media.control`.
|
|
6947
|
+
*
|
|
6948
|
+
* @example Cancel an export the user no longer wants
|
|
6949
|
+
* The running export stops, unless it is already writing its file.
|
|
6950
|
+
* `cancel` resolves `requested` when the worker will stop (the job ends
|
|
6951
|
+
* `cancelled`), `tooLate` when it is already publishing the file, and
|
|
6952
|
+
* `terminal` when the job had already ended.
|
|
6953
|
+
* ```ts
|
|
6954
|
+
* declare const job: ExportJob;
|
|
6955
|
+
* // ---cut---
|
|
6956
|
+
* const answer = await job.cancel();
|
|
6957
|
+
* if (answer === "tooLate") await plugin.osd.show("Too late: the file is being written");
|
|
6958
|
+
* ```
|
|
6959
|
+
*
|
|
6960
|
+
* @since 1.0.0
|
|
6961
|
+
*/
|
|
6060
6962
|
cancel(): Promise<"requested" | "tooLate" | "terminal">;
|
|
6061
|
-
/**
|
|
6963
|
+
/**
|
|
6964
|
+
* Read the state from the player now.
|
|
6965
|
+
*
|
|
6966
|
+
* @capability media.read
|
|
6967
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
6968
|
+
* `media.read`.
|
|
6969
|
+
*
|
|
6970
|
+
* @example Read a job's state now
|
|
6971
|
+
* The plugin reads the newest state of an export without waiting for an
|
|
6972
|
+
* event. `refresh` asks the player and resolves with the state, which
|
|
6973
|
+
* `state` then holds.
|
|
6974
|
+
* ```ts
|
|
6975
|
+
* declare const job: ExportJob;
|
|
6976
|
+
* // ---cut---
|
|
6977
|
+
* const state = await job.refresh();
|
|
6978
|
+
* console.log(state.status);
|
|
6979
|
+
* ```
|
|
6980
|
+
*
|
|
6981
|
+
* @since 1.0.0
|
|
6982
|
+
*/
|
|
6062
6983
|
refresh(): Promise<JobState>;
|
|
6063
6984
|
}
|
|
6064
6985
|
/**
|
|
@@ -6086,21 +7007,73 @@ export interface ExportJob extends Disposable {
|
|
|
6086
7007
|
* await snapshot.dispose();
|
|
6087
7008
|
* }
|
|
6088
7009
|
* ```
|
|
7010
|
+
*
|
|
7011
|
+
* @remarks
|
|
7012
|
+
* Exports run in the player's offline worker from a frozen
|
|
7013
|
+
* {@link Snapshot}: a still frame or a clip, raw or with this plugin's
|
|
7014
|
+
* effects (and the primary subtitle). Playback is not disturbed. A plugin
|
|
7015
|
+
* holds at most 4 snapshots at a time (16 in the player); release them
|
|
7016
|
+
* when done.
|
|
7017
|
+
*
|
|
7018
|
+
* @capability media.read and media.control (processed snapshots also
|
|
7019
|
+
* `playback.read` and `subtitles.read`)
|
|
7020
|
+
* @since 1.0.0
|
|
7021
|
+
* @category Media
|
|
6089
7022
|
*/
|
|
6090
7023
|
export interface MediaCapture {
|
|
6091
7024
|
/**
|
|
6092
7025
|
* Freeze the current source (and optionally this plugin set's effects)
|
|
6093
7026
|
* as an input for exports. Fails `stale` when the source changed since
|
|
6094
|
-
* `ifCurrent`; `unsupported` for
|
|
7027
|
+
* `ifCurrent`; `unsupported` for subtitles that cannot be flattened (only
|
|
7028
|
+
* plain SRT text can) or a picture over the size limits; `noSource` when
|
|
7029
|
+
* nothing is playing.
|
|
7030
|
+
*
|
|
7031
|
+
* @capability media.read and media.control
|
|
7032
|
+
* @throws `stale` (rejects) The source changed since `ifCurrent`.
|
|
7033
|
+
* @throws `noSource` (rejects) Nothing is playing.
|
|
7034
|
+
* @throws `invalid` (rejects) `at` at or after the end of the source.
|
|
7035
|
+
* @throws `busy` (rejects) This plugin holds 4 snapshots (or the player 16);
|
|
7036
|
+
* release one.
|
|
7037
|
+
* @throws `unavailable` (rejects) A processed snapshot while the picture is
|
|
7038
|
+
* still being prepared.
|
|
7039
|
+
* @throws `unsupported` (rejects) A processed snapshot the player cannot
|
|
7040
|
+
* freeze: subtitles that cannot be flattened (only plain SRT text can),
|
|
7041
|
+
* or a picture over the size limits.
|
|
7042
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7043
|
+
* `media.read` and `media.control`.
|
|
7044
|
+
*
|
|
7045
|
+
* @example Freeze the frame at the 10-second mark
|
|
7046
|
+
* The plugin freezes the frame at 0:10 of the open file, raw, to export it
|
|
7047
|
+
* later. `capture` resolves with a `Snapshot` once the player has frozen
|
|
7048
|
+
* that input; the snapshot stays valid when playback moves on or the file
|
|
7049
|
+
* changes, until it is released.
|
|
7050
|
+
* ```ts
|
|
7051
|
+
* const snapshot = await plugin.media.capture({ at: 10 }); // seconds
|
|
7052
|
+
* try {
|
|
7053
|
+
* // … export it …
|
|
7054
|
+
* } finally {
|
|
7055
|
+
* await snapshot.dispose(); // frees one of the plugin's 4 snapshot slots
|
|
7056
|
+
* }
|
|
7057
|
+
* ```
|
|
7058
|
+
*
|
|
7059
|
+
* @since 1.0.0
|
|
6095
7060
|
*/
|
|
6096
7061
|
capture(options?: CaptureOptions): Promise<Snapshot>;
|
|
6097
7062
|
}
|
|
6098
7063
|
/**
|
|
6099
7064
|
* Declaration of {@link Resolvers.add}: a web resolver
|
|
6100
7065
|
* ({@link WebResolverOptions}) or a file resolver ({@link FileResolverOptions}).
|
|
7066
|
+
*
|
|
7067
|
+
* @since 1.0.0
|
|
7068
|
+
* @category Sources
|
|
6101
7069
|
*/
|
|
6102
7070
|
export type ResolverOptions = WebResolverOptions | FileResolverOptions;
|
|
6103
|
-
/**
|
|
7071
|
+
/**
|
|
7072
|
+
* A resolver for web pages: `https://host/…` to playable stream URLs.
|
|
7073
|
+
*
|
|
7074
|
+
* @since 1.0.0
|
|
7075
|
+
* @category Sources
|
|
7076
|
+
*/
|
|
6104
7077
|
export interface WebResolverOptions {
|
|
6105
7078
|
/** Local id, unique within the plugin. */
|
|
6106
7079
|
id: string;
|
|
@@ -6165,6 +7138,9 @@ export interface WebResolverOptions {
|
|
|
6165
7138
|
* },
|
|
6166
7139
|
* });
|
|
6167
7140
|
* ```
|
|
7141
|
+
*
|
|
7142
|
+
* @since 1.5.3
|
|
7143
|
+
* @category Sources
|
|
6168
7144
|
*/
|
|
6169
7145
|
export interface FileResolverOptions {
|
|
6170
7146
|
/** Local id, unique within the plugin. */
|
|
@@ -6189,6 +7165,9 @@ export interface FileResolverOptions {
|
|
|
6189
7165
|
* failures "无法启动播放". An error thrown by `frame()` keeps its message.
|
|
6190
7166
|
* To give your own text for a file you can tell is unusable (a bad header),
|
|
6191
7167
|
* check it in `resolve` and throw there.
|
|
7168
|
+
*
|
|
7169
|
+
* @since 1.5.3
|
|
7170
|
+
* @category Sources
|
|
6192
7171
|
*/
|
|
6193
7172
|
export interface ResolvedStream {
|
|
6194
7173
|
/** Always `"stream"`. */
|
|
@@ -6207,7 +7186,38 @@ export interface ResolvedStream {
|
|
|
6207
7186
|
*/
|
|
6208
7187
|
started?(stream: Stream): void;
|
|
6209
7188
|
}
|
|
6210
|
-
/**
|
|
7189
|
+
/**
|
|
7190
|
+
* A registered resolver. Disposing it unregisters it and aborts resolutions in flight.
|
|
7191
|
+
*
|
|
7192
|
+
* @remarks
|
|
7193
|
+
* A registered resolver, from {@link Resolvers.add}. Disposing it, its
|
|
7194
|
+
* scope or stopping the plugin unregisters it and aborts resolutions in
|
|
7195
|
+
* flight; the extensions or hosts are free again for other plugins.
|
|
7196
|
+
*
|
|
7197
|
+
* @capability source.resolve
|
|
7198
|
+
*
|
|
7199
|
+
* @example Report when another plugin already handles the format
|
|
7200
|
+
* The plugin tells the user when its `.ch8` resolver could not register
|
|
7201
|
+
* because another plugin holds that extension. `ready` rejects with
|
|
7202
|
+
* `conflict`; for another plugin's claim that does not fail activation, so
|
|
7203
|
+
* the plugin keeps running without the resolver.
|
|
7204
|
+
* ```ts
|
|
7205
|
+
* const resolver = plugin.resolvers.add({
|
|
7206
|
+
* id: "chip8",
|
|
7207
|
+
* extensions: ["ch8"],
|
|
7208
|
+
* async resolve() {
|
|
7209
|
+
* throw plugin.i18n.error("Not ready yet.");
|
|
7210
|
+
* },
|
|
7211
|
+
* });
|
|
7212
|
+
* // Rejects with `conflict` when the extension is taken.
|
|
7213
|
+
* resolver.ready.catch((error: PluginError) => {
|
|
7214
|
+
* if (error.code === "conflict") void plugin.osd.show("Another plugin plays .ch8 files");
|
|
7215
|
+
* });
|
|
7216
|
+
* ```
|
|
7217
|
+
*
|
|
7218
|
+
* @since 1.0.0
|
|
7219
|
+
* @category Sources
|
|
7220
|
+
*/
|
|
6211
7221
|
export interface Resolver extends Disposable {
|
|
6212
7222
|
/**
|
|
6213
7223
|
* Resolves when the player registered it; rejects otherwise: `conflict`
|
|
@@ -6215,6 +7225,13 @@ export interface Resolver extends Disposable {
|
|
|
6215
7225
|
* plugin's: activation goes on; this plugin's own: activation fails),
|
|
6216
7226
|
* `invalid` for a malformed declaration (a player before 0.4.1 also for
|
|
6217
7227
|
* `extensions`).
|
|
7228
|
+
*
|
|
7229
|
+
* @throws `conflict` (rejects) Another resolver has the extension or the
|
|
7230
|
+
* scheme + host, or this plugin already registered this id.
|
|
7231
|
+
* @throws `busy` (rejects) The player already holds 16 resolvers.
|
|
7232
|
+
* @throws `invalid` (rejects) A malformed declaration.
|
|
7233
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7234
|
+
* `source.resolve`.
|
|
6218
7235
|
*/
|
|
6219
7236
|
readonly ready: Promise<void>;
|
|
6220
7237
|
}
|
|
@@ -6241,6 +7258,20 @@ export interface Resolver extends Disposable {
|
|
|
6241
7258
|
* },
|
|
6242
7259
|
* });
|
|
6243
7260
|
* ```
|
|
7261
|
+
*
|
|
7262
|
+
* @remarks
|
|
7263
|
+
* A resolver turns something the player cannot play by itself into
|
|
7264
|
+
* something it can: a web page address into stream URLs
|
|
7265
|
+
* ({@link WebResolverOptions}), or a local file of a format the plugin
|
|
7266
|
+
* understands into a stream the plugin generates
|
|
7267
|
+
* ({@link FileResolverOptions}). The player asks the resolver whenever
|
|
7268
|
+
* anyone opens a matching address or file (the Open dialog, the playlist,
|
|
7269
|
+
* another plugin), so the plugin does not intercept opens itself.
|
|
7270
|
+
*
|
|
7271
|
+
* @capability source.resolve (file resolvers also `producer.av`)
|
|
7272
|
+
* @see [Playback state, controls and sources](../../docs/concepts/playback.md)
|
|
7273
|
+
* @since 1.0.0
|
|
7274
|
+
* @category Sources
|
|
6244
7275
|
*/
|
|
6245
7276
|
export interface Resolvers {
|
|
6246
7277
|
/**
|
|
@@ -6249,6 +7280,29 @@ export interface Resolvers {
|
|
|
6249
7280
|
* replaced. One scheme + host pair, and one extension, has one resolver
|
|
6250
7281
|
* (`conflict` otherwise). A rejected `resolve` fails the open with the
|
|
6251
7282
|
* error's message.
|
|
7283
|
+
*
|
|
7284
|
+
* @capability source.resolve
|
|
7285
|
+
*
|
|
7286
|
+
* @example Play a site's pages through the plugin
|
|
7287
|
+
* Opening a `https://video.example.com/…` address plays the page's video.
|
|
7288
|
+
* `add` returns at once and registers in the background (`ready` reports
|
|
7289
|
+
* the answer); from then on the player calls `resolve` for every matching
|
|
7290
|
+
* open, and the error a rejected `resolve` carries is what the user sees.
|
|
7291
|
+
* ```ts
|
|
7292
|
+
* plugin.resolvers.add({
|
|
7293
|
+
* id: "example",
|
|
7294
|
+
* schemes: ["https"],
|
|
7295
|
+
* hosts: ["video.example.com"], // exact host names
|
|
7296
|
+
* async resolve(url, signal) {
|
|
7297
|
+
* const response = await fetch(url, { signal });
|
|
7298
|
+
* if (!response.ok) throw new Error(`the page answered ${response.status}`);
|
|
7299
|
+
* const page = (await response.json()) as { title: string; stream: string };
|
|
7300
|
+
* return { kind: "media", originalUrl: url, title: page.title, primary: { url: page.stream } };
|
|
7301
|
+
* },
|
|
7302
|
+
* });
|
|
7303
|
+
* ```
|
|
7304
|
+
*
|
|
7305
|
+
* @since 1.0.0
|
|
6252
7306
|
*/
|
|
6253
7307
|
add(options: ResolverOptions): Resolver;
|
|
6254
7308
|
/**
|
|
@@ -6304,6 +7358,9 @@ export interface Resolvers {
|
|
|
6304
7358
|
* The manifest of a package, as validated. The display fields and
|
|
6305
7359
|
* `minPlayerVersion` are optional (player 0.2.0); see the API reference for
|
|
6306
7360
|
* their rules.
|
|
7361
|
+
*
|
|
7362
|
+
* @since 1.0.0
|
|
7363
|
+
* @category Packages
|
|
6307
7364
|
*/
|
|
6308
7365
|
export interface PackageManifest {
|
|
6309
7366
|
/** Plugin id. */
|
|
@@ -6344,6 +7401,9 @@ export interface PackageManifest {
|
|
|
6344
7401
|
* upgrade the plugin and keep its data; links and local files count as one
|
|
6345
7402
|
* source. `bundled` marks the player's built-in plugins (set by the player
|
|
6346
7403
|
* only).
|
|
7404
|
+
*
|
|
7405
|
+
* @since 1.1.0
|
|
7406
|
+
* @category Packages
|
|
6347
7407
|
*/
|
|
6348
7408
|
export type PackageSource = {
|
|
6349
7409
|
/** A repository. */
|
|
@@ -6365,7 +7425,12 @@ export type PackageSource = {
|
|
|
6365
7425
|
/** Shipped with the player (built-in). */
|
|
6366
7426
|
kind: "bundled";
|
|
6367
7427
|
};
|
|
6368
|
-
/**
|
|
7428
|
+
/**
|
|
7429
|
+
* Options of {@link PackageCandidate.install}.
|
|
7430
|
+
*
|
|
7431
|
+
* @since 1.1.0
|
|
7432
|
+
* @category Packages
|
|
7433
|
+
*/
|
|
6369
7434
|
export interface InstallOptions {
|
|
6370
7435
|
/**
|
|
6371
7436
|
* Where the package comes from (default `{ kind: "file" }`). Repository
|
|
@@ -6380,7 +7445,34 @@ export interface InstallOptions {
|
|
|
6380
7445
|
*/
|
|
6381
7446
|
replace?: boolean;
|
|
6382
7447
|
}
|
|
6383
|
-
/**
|
|
7448
|
+
/**
|
|
7449
|
+
* A validated package directory, from {@link Packages.validate}.
|
|
7450
|
+
*
|
|
7451
|
+
* @remarks
|
|
7452
|
+
* A package the player has checked, from {@link Packages.validate} or
|
|
7453
|
+
* {@link Packages.validateArchive}: its manifest, digest and the checks
|
|
7454
|
+
* it passed. The player copies the whole package when it checks it and
|
|
7455
|
+
* installs that copy, so later changes to the directory do not matter.
|
|
7456
|
+
* Installing starts a {@link PackageChange}; the player keeps only the 32
|
|
7457
|
+
* most recent candidates.
|
|
7458
|
+
*
|
|
7459
|
+
* @capability plugins.manage
|
|
7460
|
+
*
|
|
7461
|
+
* @example Show what a package asks for before installing it
|
|
7462
|
+
* Before installing, the plugin shows the package's name and the
|
|
7463
|
+
* capabilities it declares. The candidate's `manifest` is the manifest
|
|
7464
|
+
* as the player read it.
|
|
7465
|
+
* ```ts
|
|
7466
|
+
* declare const directory: string;
|
|
7467
|
+
* // ---cut---
|
|
7468
|
+
* const candidate = await plugin.packages.validate(directory);
|
|
7469
|
+
* const { id, version, capabilities } = candidate.manifest;
|
|
7470
|
+
* await plugin.osd.show("{} {}: {}", [id, version, capabilities.join(", ")]);
|
|
7471
|
+
* ```
|
|
7472
|
+
*
|
|
7473
|
+
* @since 1.0.0
|
|
7474
|
+
* @category Packages
|
|
7475
|
+
*/
|
|
6384
7476
|
export interface PackageCandidate {
|
|
6385
7477
|
/** The package directory. */
|
|
6386
7478
|
readonly directory: string;
|
|
@@ -6397,28 +7489,138 @@ export interface PackageCandidate {
|
|
|
6397
7489
|
* player). A reserved id (`vestra` or `vestra.*`, which every built-in
|
|
6398
7490
|
* uses) fails with `reserved` (player 0.4.1; already when validated), as
|
|
6399
7491
|
* only the player installs built-in plugins.
|
|
7492
|
+
*
|
|
7493
|
+
* @capability plugins.manage
|
|
7494
|
+
* @throws `conflict` (rejects) The plugin is installed from another source
|
|
7495
|
+
* and `replace` is not set.
|
|
7496
|
+
* @throws `unsupported` (rejects) A built-in plugin's id.
|
|
7497
|
+
* @throws `reserved` (rejects) A reserved id (`vestra`, `vestra.*`).
|
|
7498
|
+
* @throws `busy` (rejects) The plugin library is being cleaned up, or the
|
|
7499
|
+
* player already keeps 64 change records.
|
|
7500
|
+
* @throws `revisionConflict` (rejects) The installed revision of this
|
|
7501
|
+
* plugin changed meanwhile; call `install` again (no new validation
|
|
7502
|
+
* needed).
|
|
7503
|
+
* @throws `stale` (rejects) The candidate is older than the 32 the player
|
|
7504
|
+
* keeps.
|
|
7505
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7506
|
+
* `plugins.manage`.
|
|
7507
|
+
*
|
|
7508
|
+
* @example Replace a plugin installed from another source
|
|
7509
|
+
* The new package replaces the installed plugin even though it came from
|
|
7510
|
+
* another source. Without `replace` the player refuses with `conflict`;
|
|
7511
|
+
* the same source simply upgrades and keeps the plugin's data.
|
|
7512
|
+
* ```ts
|
|
7513
|
+
* declare const candidate: PackageCandidate;
|
|
7514
|
+
* // ---cut---
|
|
7515
|
+
* const change = await candidate.install({ replace: true });
|
|
7516
|
+
* await change.done; // "committed" once the new plugin runs
|
|
7517
|
+
* ```
|
|
7518
|
+
*
|
|
7519
|
+
* @since 1.0.0
|
|
6400
7520
|
*/
|
|
6401
7521
|
install(options?: InstallOptions): Promise<PackageChange>;
|
|
6402
7522
|
}
|
|
6403
|
-
/**
|
|
7523
|
+
/**
|
|
7524
|
+
* The progress of an install or uninstall.
|
|
7525
|
+
*
|
|
7526
|
+
* @since 1.0.0
|
|
7527
|
+
* @category Packages
|
|
7528
|
+
*/
|
|
6404
7529
|
export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
|
|
6405
7530
|
/**
|
|
6406
7531
|
* An install or uninstall in progress. The SDK never submits a
|
|
6407
7532
|
* change twice: after a lost reply it only queries the same request.
|
|
7533
|
+
*
|
|
7534
|
+
* @remarks
|
|
7535
|
+
* An install or uninstall the player carries out in steps (`preparing`,
|
|
7536
|
+
* `activating`, possibly `restoring`) until a terminal status. The SDK
|
|
7537
|
+
* asks the player for the status about every 400 ms until then; `done`
|
|
7538
|
+
* resolves with the terminal status and does not reject for a failed
|
|
7539
|
+
* change: `message` says why.
|
|
7540
|
+
*
|
|
7541
|
+
* @capability plugins.manage
|
|
7542
|
+
*
|
|
7543
|
+
* @example Report each step of an install
|
|
7544
|
+
* The OSD shows each step of the install and the end result. `onChange`
|
|
7545
|
+
* runs for every new status; `done` resolves when the change is over.
|
|
7546
|
+
* ```ts
|
|
7547
|
+
* declare const change: PackageChange;
|
|
7548
|
+
* // ---cut---
|
|
7549
|
+
* change.onChange((status) => void plugin.osd.show("Install: {}", [status]));
|
|
7550
|
+
* const end = await change.done;
|
|
7551
|
+
* if (end !== "committed") await plugin.osd.show("Not installed: {}", [change.message ?? end]);
|
|
7552
|
+
* ```
|
|
7553
|
+
*
|
|
7554
|
+
* @since 1.0.0
|
|
7555
|
+
* @category Packages
|
|
6408
7556
|
*/
|
|
6409
7557
|
export interface PackageChange {
|
|
6410
7558
|
/** Current status. */
|
|
6411
7559
|
readonly status: PackageChangeStatus;
|
|
6412
7560
|
/** Details for failures, `null` otherwise. */
|
|
6413
7561
|
readonly message: string | null;
|
|
6414
|
-
/**
|
|
7562
|
+
/**
|
|
7563
|
+
* The status changed.
|
|
7564
|
+
*
|
|
7565
|
+
* @capability plugins.manage
|
|
7566
|
+
*
|
|
7567
|
+
* @example Close a progress window when the change ends
|
|
7568
|
+
* The plugin's progress window closes once the install is over, whatever
|
|
7569
|
+
* the outcome. The listener receives each new status; the terminal ones
|
|
7570
|
+
* are `committed`, `cancelled`, `failed`, `failedDegraded` and `tooLate`
|
|
7571
|
+
* (`tooLate` only answers a `cancel` of a change that had committed, and
|
|
7572
|
+
* `committed` may follow it).
|
|
7573
|
+
* ```ts
|
|
7574
|
+
* declare const change: PackageChange;
|
|
7575
|
+
* declare const progress: Window;
|
|
7576
|
+
* // ---cut---
|
|
7577
|
+
* change.onChange((status) => {
|
|
7578
|
+
* if (["committed", "cancelled", "failed", "failedDegraded", "tooLate"].includes(status))
|
|
7579
|
+
* void progress.close();
|
|
7580
|
+
* });
|
|
7581
|
+
* ```
|
|
7582
|
+
*
|
|
7583
|
+
* @since 1.0.0
|
|
7584
|
+
*/
|
|
6415
7585
|
onChange(listener: (status: PackageChangeStatus) => void): Disposable;
|
|
6416
|
-
/**
|
|
7586
|
+
/**
|
|
7587
|
+
* Resolves with the final status.
|
|
7588
|
+
*
|
|
7589
|
+
* @throws `missing` (rejects) The player no longer knows the change (a
|
|
7590
|
+
* `HostError` with that code); a failed change is a terminal status,
|
|
7591
|
+
* not a rejection.
|
|
7592
|
+
*/
|
|
6417
7593
|
readonly done: Promise<PackageChangeStatus>;
|
|
6418
|
-
/**
|
|
7594
|
+
/**
|
|
7595
|
+
* Ask to cancel (it may be `tooLate`).
|
|
7596
|
+
*
|
|
7597
|
+
* @capability plugins.manage
|
|
7598
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7599
|
+
* `plugins.manage`.
|
|
7600
|
+
*
|
|
7601
|
+
* @example Cancel an install the user no longer wants
|
|
7602
|
+
* The install stops if it has not gone too far. `cancel` asks the player;
|
|
7603
|
+
* the change then ends `cancelled`. When the change had already committed
|
|
7604
|
+
* the player answers `tooLate` and `done` still resolves `committed`; a
|
|
7605
|
+
* change that is restoring cannot be cancelled and ends `failed` or
|
|
7606
|
+
* `failedDegraded`.
|
|
7607
|
+
* ```ts
|
|
7608
|
+
* declare const change: PackageChange;
|
|
7609
|
+
* // ---cut---
|
|
7610
|
+
* await change.cancel();
|
|
7611
|
+
* console.log(await change.done); // "cancelled"; "committed" when it was too late
|
|
7612
|
+
* ```
|
|
7613
|
+
*
|
|
7614
|
+
* @since 1.0.0
|
|
7615
|
+
*/
|
|
6419
7616
|
cancel(): Promise<void>;
|
|
6420
7617
|
}
|
|
6421
|
-
/**
|
|
7618
|
+
/**
|
|
7619
|
+
* One plugin as the player knows it (`packages.list`).
|
|
7620
|
+
*
|
|
7621
|
+
* @since 1.0.0
|
|
7622
|
+
* @category Packages
|
|
7623
|
+
*/
|
|
6422
7624
|
export interface PluginInfo {
|
|
6423
7625
|
/** Manifest id. */
|
|
6424
7626
|
id: string;
|
|
@@ -6477,6 +7679,8 @@ export interface PluginInfo {
|
|
|
6477
7679
|
* `description` and `tags` as the player shows them.
|
|
6478
7680
|
*
|
|
6479
7681
|
* @since 1.5.9
|
|
7682
|
+
*
|
|
7683
|
+
* @category Packages
|
|
6480
7684
|
*/
|
|
6481
7685
|
export interface PluginDisplay {
|
|
6482
7686
|
/** `name`, translated; `null` when the manifest has none. */
|
|
@@ -6492,6 +7696,8 @@ export interface PluginDisplay {
|
|
|
6492
7696
|
* One refusal in {@link PluginInfo.warnings}.
|
|
6493
7697
|
*
|
|
6494
7698
|
* @since 1.5.2
|
|
7699
|
+
*
|
|
7700
|
+
* @category Packages
|
|
6495
7701
|
*/
|
|
6496
7702
|
export interface PluginWarning {
|
|
6497
7703
|
/**
|
|
@@ -6528,6 +7734,17 @@ export interface PluginWarning {
|
|
|
6528
7734
|
* // `message` says why a change did not commit.
|
|
6529
7735
|
* if ((await change.done) !== "committed") throw new Error(change.message ?? "install failed");
|
|
6530
7736
|
* ```
|
|
7737
|
+
*
|
|
7738
|
+
* @remarks
|
|
7739
|
+
* The plugins the player knows: installed, built-in and this run's
|
|
7740
|
+
* development loads. Reading the list needs no capability; installing,
|
|
7741
|
+
* enabling, reloading and uninstalling need `plugins.manage` and act on
|
|
7742
|
+
* other plugins, never on the calling one. Built-in plugins update with
|
|
7743
|
+
* the player and cannot be installed, disabled or uninstalled.
|
|
7744
|
+
*
|
|
7745
|
+
* @capability None for `list` and `onChange`; plugins.manage for the rest.
|
|
7746
|
+
* @since 1.0.0
|
|
7747
|
+
* @category Packages
|
|
6531
7748
|
*/
|
|
6532
7749
|
export interface Packages {
|
|
6533
7750
|
/**
|
|
@@ -6538,6 +7755,37 @@ export interface Packages {
|
|
|
6538
7755
|
* The player keeps the 32 most recent candidates of all plugins; an
|
|
6539
7756
|
* older one's `install` fails with `stale` (validate again). Before
|
|
6540
7757
|
* player 0.4.2 the 33rd validation of a run failed with `busy` instead.
|
|
7758
|
+
*
|
|
7759
|
+
* @capability plugins.manage
|
|
7760
|
+
* @throws `host` (rejects) Not a package directory, or a manifest that
|
|
7761
|
+
* breaks the rules (another `apiVersion` included); `message` says why.
|
|
7762
|
+
* @throws `invalid` (rejects) A bad request (a path that is not a string).
|
|
7763
|
+
* @throws `javascript` (rejects) The entry module does not parse.
|
|
7764
|
+
* @throws `slint` (rejects) A Slint file does not compile.
|
|
7765
|
+
* @throws `reserved` (rejects) A reserved id (`vestra`, `vestra.*`).
|
|
7766
|
+
* @throws `versionMismatch` (rejects) The package's `minPlayerVersion` is
|
|
7767
|
+
* newer than the player.
|
|
7768
|
+
* @throws `disabled` (rejects) The calling plugin is stopping.
|
|
7769
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7770
|
+
* `plugins.manage`.
|
|
7771
|
+
*
|
|
7772
|
+
* @example Check a package directory before installing it
|
|
7773
|
+
* The plugin checks a folder it unpacked and installs it if the player
|
|
7774
|
+
* accepts it. `validate` runs the player's checks and resolves with a
|
|
7775
|
+
* candidate; a package that fails them rejects with the reason.
|
|
7776
|
+
* ```ts
|
|
7777
|
+
* declare const directory: string;
|
|
7778
|
+
* // ---cut---
|
|
7779
|
+
* try {
|
|
7780
|
+
* // Rejects with the reason when the package fails the checks.
|
|
7781
|
+
* const candidate = await plugin.packages.validate(directory);
|
|
7782
|
+
* await (await candidate.install()).done;
|
|
7783
|
+
* } catch (error) {
|
|
7784
|
+
* await plugin.osd.show("Not a valid plugin: {}", [(error as Error).message]);
|
|
7785
|
+
* }
|
|
7786
|
+
* ```
|
|
7787
|
+
*
|
|
7788
|
+
* @since 1.0.0
|
|
6541
7789
|
*/
|
|
6542
7790
|
validate(directory: string): Promise<PackageCandidate>;
|
|
6543
7791
|
/**
|
|
@@ -6545,19 +7793,97 @@ export interface Packages {
|
|
|
6545
7793
|
* same checks as {@link Packages.validate} after unpacking. Check the
|
|
6546
7794
|
* file's sha256 yourself before calling when you know it. A reserved id
|
|
6547
7795
|
* fails with `reserved`, as in {@link Packages.validate}.
|
|
7796
|
+
*
|
|
7797
|
+
* @capability plugins.manage
|
|
7798
|
+
* @throws `invalid` (rejects) The path does not end in `.vxp`, or the
|
|
7799
|
+
* archive cannot be unpacked (over 64 MB, too many files, a damaged zip,
|
|
7800
|
+
* an unsafe path).
|
|
7801
|
+
* @throws `host` (rejects) The unpacked package or its manifest breaks the
|
|
7802
|
+
* rules; `message` says why.
|
|
7803
|
+
* @throws `javascript` (rejects) The entry module does not parse.
|
|
7804
|
+
* @throws `slint` (rejects) A Slint file does not compile.
|
|
7805
|
+
* @throws `reserved` (rejects) A reserved id.
|
|
7806
|
+
* @throws `versionMismatch` (rejects) The package's `minPlayerVersion` is
|
|
7807
|
+
* newer than the player.
|
|
7808
|
+
* @throws `disabled` (rejects) The calling plugin is stopping.
|
|
7809
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7810
|
+
* `plugins.manage`.
|
|
7811
|
+
*
|
|
7812
|
+
* @example Install a downloaded package file
|
|
7813
|
+
* A `.vxp` the plugin downloaded is checked and installed. The player
|
|
7814
|
+
* unpacks and checks it like a directory; checking the file's sha256
|
|
7815
|
+
* first, when the store publishes one, is up to the plugin.
|
|
7816
|
+
* ```ts
|
|
7817
|
+
* declare const file: string;
|
|
7818
|
+
* // ---cut---
|
|
7819
|
+
* const candidate = await plugin.packages.validateArchive(file); // a .vxp path
|
|
7820
|
+
* const change = await candidate.install();
|
|
7821
|
+
* await plugin.osd.show("Install: {}", [await change.done]);
|
|
7822
|
+
* ```
|
|
7823
|
+
*
|
|
7824
|
+
* @since 1.1.0
|
|
6548
7825
|
*/
|
|
6549
7826
|
validateArchive(file: string): Promise<PackageCandidate>;
|
|
6550
|
-
/**
|
|
7827
|
+
/**
|
|
7828
|
+
* Every plugin: running, installed, and this run's development loads. No capability.
|
|
7829
|
+
*
|
|
7830
|
+
* @capability None.
|
|
7831
|
+
* @throws `Error` (rejects) Only when the player cannot be asked; no
|
|
7832
|
+
* capability is needed.
|
|
7833
|
+
*
|
|
7834
|
+
* @example Show which plugins are running
|
|
7835
|
+
* The plugin lists the running plugins by name. `list` asks the player
|
|
7836
|
+
* and resolves with every plugin it knows, with its state, origin and
|
|
7837
|
+
* display texts in the interface language.
|
|
7838
|
+
* ```ts
|
|
7839
|
+
* const plugins = await plugin.packages.list();
|
|
7840
|
+
* const running = plugins.filter((info) => info.state === "running");
|
|
7841
|
+
* console.log(running.map((info) => info.display?.name ?? info.id));
|
|
7842
|
+
* ```
|
|
7843
|
+
*
|
|
7844
|
+
* @since 1.0.0
|
|
7845
|
+
*/
|
|
6551
7846
|
list(): Promise<PluginInfo[]>;
|
|
6552
|
-
/**
|
|
7847
|
+
/**
|
|
7848
|
+
* The new list after any start, stop, failure, install or uninstall. No capability.
|
|
7849
|
+
*
|
|
7850
|
+
* @capability None.
|
|
7851
|
+
*
|
|
7852
|
+
* @example Refresh a plugin list after any change
|
|
7853
|
+
* A store window's list updates when a plugin starts, stops, fails, is
|
|
7854
|
+
* installed or uninstalled. The listener receives the new list each time.
|
|
7855
|
+
* ```ts
|
|
7856
|
+
* plugin.packages.onChange((plugins) => console.log(plugins.length, "plugins"));
|
|
7857
|
+
* ```
|
|
7858
|
+
*
|
|
7859
|
+
* @since 1.0.0
|
|
7860
|
+
*/
|
|
6553
7861
|
onChange(listener: (plugins: PluginInfo[]) => void): Disposable;
|
|
6554
7862
|
/**
|
|
6555
7863
|
* Stop or start a plugin (`plugins.manage`). An installed plugin stays
|
|
6556
7864
|
* disabled across launches; a development load only for this run.
|
|
6557
7865
|
* `invalid` to disable oneself, `missing` unknown.
|
|
6558
|
-
* `busy` while it is being installed. `unsupported` to disable a built-in
|
|
7866
|
+
* `busy` while it is being installed or reloaded. `unsupported` to disable a built-in
|
|
6559
7867
|
* ({@link PluginInfo.builtin}): users manage third-party plugins only
|
|
6560
7868
|
* (players released with SDK 1.5.18 on; older ones disabled built-ins too).
|
|
7869
|
+
*
|
|
7870
|
+
* @capability plugins.manage
|
|
7871
|
+
* @throws `invalid` (rejects) Disabling the calling plugin itself.
|
|
7872
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7873
|
+
* @throws `busy` (rejects) The plugin is being installed or reloaded.
|
|
7874
|
+
* @throws `unsupported` (rejects) A built-in plugin.
|
|
7875
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7876
|
+
* `plugins.manage`.
|
|
7877
|
+
*
|
|
7878
|
+
* @example Turn a third-party plugin off
|
|
7879
|
+
* The plugin stops another plugin and keeps it disabled across launches
|
|
7880
|
+
* until it is enabled again. Built-in plugins cannot be disabled
|
|
7881
|
+
* (`unsupported`).
|
|
7882
|
+
* ```ts
|
|
7883
|
+
* await plugin.packages.setEnabled("someone.demo", false);
|
|
7884
|
+
* ```
|
|
7885
|
+
*
|
|
7886
|
+
* @since 1.0.0
|
|
6561
7887
|
*/
|
|
6562
7888
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
6563
7889
|
/**
|
|
@@ -6578,6 +7904,23 @@ export interface Packages {
|
|
|
6578
7904
|
* @player 0.5.1; older players reject it as an unknown request
|
|
6579
7905
|
* (`unsupported`).
|
|
6580
7906
|
* @since 1.5.19
|
|
7907
|
+
*
|
|
7908
|
+
* @capability plugins.manage
|
|
7909
|
+
* @throws `invalid` (rejects) The calling plugin itself, or a disabled
|
|
7910
|
+
* plugin.
|
|
7911
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7912
|
+
* @throws `busy` (rejects) The plugin is being installed or reloaded.
|
|
7913
|
+
* @throws `unsupported` (rejects) A player before 0.5.1.
|
|
7914
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7915
|
+
* `plugins.manage`.
|
|
7916
|
+
*
|
|
7917
|
+
* @example Restart a plugin that misbehaves
|
|
7918
|
+
* The other plugin is stopped and started again from the same package and
|
|
7919
|
+
* data. `reload` resolves once the old process is stopped and the start is
|
|
7920
|
+
* scheduled; `onChange` then shows `starting` and `running` (or `failed`).
|
|
7921
|
+
* ```ts
|
|
7922
|
+
* await plugin.packages.reload("someone.demo");
|
|
7923
|
+
* ```
|
|
6581
7924
|
*/
|
|
6582
7925
|
reload(id: string): Promise<void>;
|
|
6583
7926
|
/**
|
|
@@ -6586,19 +7929,51 @@ export interface Packages {
|
|
|
6586
7929
|
* reconnects to its data. Its user bindings stay (as orphans).
|
|
6587
7930
|
* `unsupported` for built-ins and development loads, `missing`
|
|
6588
7931
|
* unknown, `invalid` for oneself.
|
|
7932
|
+
*
|
|
7933
|
+
* @capability plugins.manage
|
|
7934
|
+
* @throws `unsupported` (rejects) A built-in plugin or a development load.
|
|
7935
|
+
* @throws `missing` (rejects) An unknown id.
|
|
7936
|
+
* @throws `invalid` (rejects) The calling plugin itself.
|
|
7937
|
+
* @throws `busy` (rejects) The plugin library is being cleaned up, or that
|
|
7938
|
+
* plugin is being installed or reloaded.
|
|
7939
|
+
* @throws `revisionConflict` (rejects) The installed revision of that
|
|
7940
|
+
* plugin changed meanwhile; call `uninstall` again.
|
|
7941
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
7942
|
+
* `plugins.manage`.
|
|
7943
|
+
*
|
|
7944
|
+
* @example Uninstall a plugin together with its data
|
|
7945
|
+
* The other plugin is removed and its data directory deleted, so a later
|
|
7946
|
+
* reinstall starts fresh. Without `removeData` a reinstall from the same
|
|
7947
|
+
* source finds its data again.
|
|
7948
|
+
* ```ts
|
|
7949
|
+
* const change = await plugin.packages.uninstall("someone.demo", { removeData: true });
|
|
7950
|
+
* await change.done;
|
|
7951
|
+
* ```
|
|
7952
|
+
*
|
|
7953
|
+
* @since 1.0.0
|
|
6589
7954
|
*/
|
|
6590
7955
|
uninstall(id: string, options?: {
|
|
6591
7956
|
removeData?: boolean;
|
|
6592
7957
|
}): Promise<PackageChange>;
|
|
6593
7958
|
}
|
|
6594
|
-
/**
|
|
7959
|
+
/**
|
|
7960
|
+
* A command: manifest id (`"host"` for the player's own) and command id.
|
|
7961
|
+
*
|
|
7962
|
+
* @since 1.0.0
|
|
7963
|
+
* @category Commands
|
|
7964
|
+
*/
|
|
6595
7965
|
export interface BindingRef {
|
|
6596
7966
|
/** Manifest id, or `"host"`. */
|
|
6597
7967
|
plugin: string;
|
|
6598
7968
|
/** Command id within that plugin. */
|
|
6599
7969
|
command: string;
|
|
6600
7970
|
}
|
|
6601
|
-
/**
|
|
7971
|
+
/**
|
|
7972
|
+
* One command's bindings, from {@link Bindings.list}.
|
|
7973
|
+
*
|
|
7974
|
+
* @since 1.0.0
|
|
7975
|
+
* @category Commands
|
|
7976
|
+
*/
|
|
6602
7977
|
export interface BindingInfo extends BindingRef {
|
|
6603
7978
|
/** Menu title. */
|
|
6604
7979
|
title: string;
|
|
@@ -6648,9 +8023,40 @@ export interface BindingInfo extends BindingRef {
|
|
|
6648
8023
|
* // later
|
|
6649
8024
|
* await plugin.bindings.reset({ plugin: "host", command: "mute" });
|
|
6650
8025
|
* ```
|
|
8026
|
+
*
|
|
8027
|
+
* @remarks
|
|
8028
|
+
* The player's binding table: every command's default keys (from
|
|
8029
|
+
* {@link CommandOptions.bindings}, the player's own as `host/<action>`)
|
|
8030
|
+
* and the user's overrides on top, saved across launches. A key-settings
|
|
8031
|
+
* plugin lists the table, writes overrides and resets them; the player
|
|
8032
|
+
* applies the change at once to every running plugin.
|
|
8033
|
+
*
|
|
8034
|
+
* @capability bindings.manage
|
|
8035
|
+
* @see [Commands, menus and key bindings](../../docs/concepts/commands.md)
|
|
8036
|
+
* @since 1.0.0
|
|
8037
|
+
* @category Commands
|
|
6651
8038
|
*/
|
|
6652
8039
|
export interface Bindings {
|
|
6653
|
-
/**
|
|
8040
|
+
/**
|
|
8041
|
+
* Every declared command, and overrides whose command is gone.
|
|
8042
|
+
*
|
|
8043
|
+
* @capability bindings.manage
|
|
8044
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
8045
|
+
* `bindings.manage`.
|
|
8046
|
+
*
|
|
8047
|
+
* @example Find the commands that use a key
|
|
8048
|
+
* A key-settings window shows which commands Ctrl+K is bound to now. `list`
|
|
8049
|
+
* resolves with every declared command and its current bindings (the
|
|
8050
|
+
* defaults with the user's overrides applied) and with the overrides
|
|
8051
|
+
* whose command is not running (`orphans`).
|
|
8052
|
+
* ```ts
|
|
8053
|
+
* const { commands } = await plugin.bindings.list();
|
|
8054
|
+
* const users = commands.filter((command) => command.current.includes("Ctrl+K"));
|
|
8055
|
+
* console.log(users.map((command) => `${command.plugin}/${command.command}`)); // "host" for the player
|
|
8056
|
+
* ```
|
|
8057
|
+
*
|
|
8058
|
+
* @since 1.0.0
|
|
8059
|
+
*/
|
|
6654
8060
|
list(): Promise<{
|
|
6655
8061
|
/** Every declared command (running plugins and the player). */
|
|
6656
8062
|
commands: BindingInfo[];
|
|
@@ -6667,11 +8073,65 @@ export interface Bindings {
|
|
|
6667
8073
|
* `invalid` for a bad binding, `missing` for an unknown command that has
|
|
6668
8074
|
* no override either.
|
|
6669
8075
|
* At most 16 inputs, syntax as {@link CommandOptions.bindings}.
|
|
8076
|
+
*
|
|
8077
|
+
* @capability bindings.manage
|
|
8078
|
+
* @throws `invalid` (rejects) A malformed binding, or more than 16.
|
|
8079
|
+
* @throws `missing` (rejects) An unknown command that has no override
|
|
8080
|
+
* either.
|
|
8081
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
8082
|
+
* `bindings.manage`.
|
|
8083
|
+
*
|
|
8084
|
+
* @example Give the player's Mute command a second key
|
|
8085
|
+
* Mute now answers to M and to Ctrl+M. `set` replaces the command's
|
|
8086
|
+
* bindings with the list given (an override saved across launches), so it
|
|
8087
|
+
* repeats the default it keeps; `[]` would unbind it.
|
|
8088
|
+
* ```ts
|
|
8089
|
+
* await plugin.bindings.set({ plugin: "host", command: "mute" }, ["M", "Ctrl+M"]);
|
|
8090
|
+
* ```
|
|
8091
|
+
*
|
|
8092
|
+
* @since 1.0.0
|
|
6670
8093
|
*/
|
|
6671
8094
|
set(ref: BindingRef, inputs: string[]): Promise<void>;
|
|
6672
|
-
/**
|
|
8095
|
+
/**
|
|
8096
|
+
* Back to the defaults: one command, or every override when omitted.
|
|
8097
|
+
*
|
|
8098
|
+
* @capability bindings.manage
|
|
8099
|
+
* @throws `missing` (rejects) A command that has no override and is not
|
|
8100
|
+
* declared.
|
|
8101
|
+
* @throws `capability` (rejects) The manifest does not declare
|
|
8102
|
+
* `bindings.manage`.
|
|
8103
|
+
*
|
|
8104
|
+
* @example Restore every default key
|
|
8105
|
+
* A "Restore defaults" button removes all of the user's overrides, so
|
|
8106
|
+
* every command has its declared keys again. Without an argument `reset`
|
|
8107
|
+
* clears every override; with a reference only that command's.
|
|
8108
|
+
* ```ts
|
|
8109
|
+
* await plugin.bindings.reset(); // every override
|
|
8110
|
+
* ```
|
|
8111
|
+
*
|
|
8112
|
+
* @since 1.0.0
|
|
8113
|
+
*/
|
|
6673
8114
|
reset(ref?: BindingRef): Promise<void>;
|
|
6674
|
-
/**
|
|
8115
|
+
/**
|
|
8116
|
+
* Declarations or overrides changed (read `list()` again). At most every 200 ms.
|
|
8117
|
+
*
|
|
8118
|
+
* @capability bindings.manage
|
|
8119
|
+
*
|
|
8120
|
+
* @example Refresh a key list when bindings change
|
|
8121
|
+
* The plugin's key list stays current when the user changes a key in the
|
|
8122
|
+
* player or another plugin starts. The listener carries no data and runs
|
|
8123
|
+
* at most every 200 ms; the plugin reads the table again with `list`.
|
|
8124
|
+
* ```ts
|
|
8125
|
+
* declare function render(commands: BindingInfo[]): void;
|
|
8126
|
+
* // ---cut---
|
|
8127
|
+
* plugin.bindings.onChange(async () => {
|
|
8128
|
+
* const { commands } = await plugin.bindings.list(); // read the new table
|
|
8129
|
+
* render(commands);
|
|
8130
|
+
* });
|
|
8131
|
+
* ```
|
|
8132
|
+
*
|
|
8133
|
+
* @since 1.0.0
|
|
8134
|
+
*/
|
|
6675
8135
|
onChange(listener: () => void): Disposable;
|
|
6676
8136
|
}
|
|
6677
8137
|
/**
|