@vestra-player/sdk 1.6.7 → 1.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/bin/vestra-plugin.mjs +1 -84
- package/dist/api-requests.js +5 -1
- package/dist/commands.d.ts +6 -7
- package/dist/commands.js +6 -7
- package/dist/docs-tree.json.gz +0 -0
- package/dist/host-capabilities.js +4 -1
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/osd.d.ts +4 -0
- package/dist/plugin/osd.js +26 -1
- package/dist/plugin/testing.d.ts +100 -2
- package/dist/plugin/testing.js +160 -5
- package/dist/plugin/timeline.d.ts +40 -3
- package/dist/plugin/timeline.js +102 -10
- package/dist/plugin/types.d.ts +204 -21
- package/dist/wire.d.ts +27 -4
- package/dist/wire.js +8 -1
- package/llms.txt +4 -4
- package/package.json +1 -1
- package/slint/vestra/bar-buttons.slint +72 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.9
|
|
4
|
+
|
|
5
|
+
**Who must change: nobody.** Additions only: timeline ranges, OSD messages with action buttons, a limits constant, a wire method and `TestHost` support, and documentation of a bottom bar's `content-full-width`. `apiVersion` stays `vestra/1`, no new capability (`timeline.marks`, `playback.read` and `ui.osd` cover them). A plugin that does not use them sends exactly what it sent before; `onChange` of `plugin.timeline` still runs only when the marks change. One that uses them needs player 0.7.6 (`"minPlayerVersion": "0.7.6"`): the player builds `plugin` from its own SDK, so on older players `setRanges` and `ranges` do not exist (a `TypeError`), and `osd.show` there takes no options.
|
|
6
|
+
|
|
7
|
+
- **Timeline ranges** (player 0.7.6): `TimelineLayer.setRanges(ranges, { ifCurrent? })` puts spans of the media on the seek bar, each `{ start, end, style, title? }` with `style` `"selection"` (a span the user picked to act on, such as a clip to export; drawn in the accent colour) or `"region"` (a part of the media the plugin describes; drawn dimmer). A layer's marks and ranges are replaced on their own (`set` keeps the ranges, `setRanges` keeps the marks); disposing the layer, its scope or stopping the plugin removes both, and a new source clears every layer. At most 100 ranges per layer, `0 ≤ start < end`, `end` within the media, titles ≤ 100 characters (`TIMELINE_LIMITS`, new, with the marks' 1000); failures go to `onError` as for marks. `plugin.timeline.ranges` (new, `playback.read`) is every layer's ranges merged by start, with `TimelineRange` (`start`, `end`, `style`, `title`, `plugin`); read it in a binding to follow it. Wire: `timeline.setRanges` (needs `timeline.marks`), and `ranges` beside `marks` in `timeline.marks` and `timeline.changed`.
|
|
8
|
+
- **OSD messages with actions** (player 0.7.6): `plugin.osd.show(text, args?, { actions })` ends the message in up to two buttons (`OsdAction`: `title`, translated like the text, 1–40 characters; `command`, the local id of one of the plugin's own commands). The message stays about 4 seconds instead of 2 and does not fade while the pointer is on it and moving (it goes 2 seconds after the pointer leaves or rests, and at the latest 30 seconds after it showed); a click runs the command as if picked from a menu and removes the message. It stays one line, and nothing in it is created or removed while the pointer moves. A newer message replaces it with its buttons; the buttons go when the plugin stops, and while they show the plugin is not stopped for being idle. `osd.show` rejects with `invalid` for more than two actions, a bad title or a command the plugin has not declared. `OSD_LIMITS` gains `actions` (2) and `actionChars` (40). Older players ignore `actions`.
|
|
9
|
+
- **`TestHost`**: `timeline.rangeLayers`, `timeline.ranges` and `setTimelineRanges(pluginId, ranges)` (another plugin's ranges); `timeline.setRanges` checked as the player does; `osdActions` (each message's buttons, in step with `osd`) and `pressOsdAction(index)`, which runs the command like a click.
|
|
10
|
+
- **A bottom bar's `content-full-width`** (player 0.7.6, docs only): a bar that drops controls of its own also reports its row with every control shown in `content-full-width`, a public `length` property declared like `content-min-width`. The player then gives plugin buttons only the width beyond it, so every plugin button moves to » before the bar drops a control, and narrowing the window never brings a button back. A bar with only `content-min-width` keeps the earlier rule. [Writing a bottom bar](docs/guidelines/interface.md#writing-a-bottom-bar) and [Reference: theme](docs/reference/theme.md#bar-buttons) say so.
|
|
11
|
+
|
|
12
|
+
## 1.6.8
|
|
13
|
+
|
|
14
|
+
**Who must change: nobody; authors of a bottom bar should move to `content-min-width`.** Documentation, a read-only Slint property and a removed CLI warning: no change to the TypeScript API, the wire or `TestHost`, `apiVersion` stays `vestra/1`, no new capability. A plugin's `button` declarations stay as they are. A bottom bar that still places `BarButtons` keeps working on player 0.7.6 (it shows that strip, and the player draws no second one); it is deprecated and goes in a later patch.
|
|
15
|
+
|
|
16
|
+
- **The player places the bottom bar's button strip** (player 0.7.6, menu redesign step 3a). It draws the strip at the right end of the bottom slot, also when no plugin mounts a bottom bar (in a band of its own): the plugins' buttons, a » menu, the player's **Command Palette** (`host/palette`, a magnifier) and **Toggle Sidebar**. [Bottom-bar buttons](docs/explanation/commands.md#bottom-bar-buttons) now reads: buttons are ordered by `order`, then by plugin id; lower `order` comes first and stays visible longest; when the strip runs out of room, or more than 8 plugin buttons are declared, the remaining buttons move to the » menu, in the same order; a button is never dropped. Before player 0.7.6 a narrow strip lost its lowest orders first and the player left out all but the eight highest; `BAR_BUTTON_LIMITS.total` is now what the strip holds. [Reference: commands](docs/reference/commands.md#bottom-bar-buttons), [Reference: limits](docs/reference/limits.md#bottom-bar-buttons), `CommandButton`, `CommandOptions.button` and `BAR_BUTTON_LIMITS` say so.
|
|
17
|
+
- **Writing a bottom bar** ([Interface guidelines](docs/guidelines/interface.md#writing-a-bottom-bar)): a bar places nothing. It reports the least width of its own controls in a `content-min-width` length property on the mounted component (the player gives the strip the rest, so the time label is never covered) and ends its row at the strip, reading `BarButtonsHost.width` (new, read only; `fixed` for an optional control kept ahead of plugin buttons) from `@vestra/bar-buttons.slint`. The file stays in the Slint library copy for that global; its `BarButtons` component is deprecated.
|
|
18
|
+
- **`@vestra/bar-buttons.slint`** (the read-only copy of the player's widget library, synced): `BarButtonsHost` gains `width`, `fixed`, `shown` and `more` (out: the strip's width, the cells a bar must leave free (the player's own and the » cell), the plugin buttons in the strip, whether » shows) and the player's `palette`, `overflow` and `room`; the comments describe the player's strip and mark `BarButtons` deprecated.
|
|
19
|
+
- **`vestra-plugin check` no longer warns** that a bottom bar does not place `BarButtons`: since player 0.7.6 that is the right thing to do.
|
|
20
|
+
|
|
3
21
|
## 1.6.7
|
|
4
22
|
|
|
5
23
|
**Who must change: nobody.** The 1.6.6 release below, published again under a new number: its npm publish failed (the provenance transparency log returned 500, then 409 for the same tarball), so 1.6.6 never reached npm. Nothing else changed.
|
package/bin/vestra-plugin.mjs
CHANGED
|
@@ -81,7 +81,6 @@ import {
|
|
|
81
81
|
basename,
|
|
82
82
|
dirname,
|
|
83
83
|
extname,
|
|
84
|
-
isAbsolute,
|
|
85
84
|
join,
|
|
86
85
|
relative,
|
|
87
86
|
resolve,
|
|
@@ -363,10 +362,7 @@ async function check(target, report = null) {
|
|
|
363
362
|
}
|
|
364
363
|
say(`${target.name}: capabilities declared for every API use`);
|
|
365
364
|
if (report) return;
|
|
366
|
-
for (const line of
|
|
367
|
-
...(await bottomBarWarnings(target)),
|
|
368
|
-
...(await forWarnings(target)),
|
|
369
|
-
])
|
|
365
|
+
for (const line of await forWarnings(target))
|
|
370
366
|
console.log(`${target.name}: warning: ${line}`);
|
|
371
367
|
// Translation warnings never fail the check.
|
|
372
368
|
if (present(join(target.directory, "locales"))) {
|
|
@@ -432,85 +428,6 @@ function slintCode(text) {
|
|
|
432
428
|
return out;
|
|
433
429
|
}
|
|
434
430
|
|
|
435
|
-
/** Whether `path` lies inside `directory` (not a sibling sharing its prefix). */
|
|
436
|
-
function inside(directory, path) {
|
|
437
|
-
const rel = relative(resolve(directory), path);
|
|
438
|
-
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Whether the Slint file at `file` (relative to the plugin directory)
|
|
443
|
-
* imports `BarButtons` from `@vestra/bar-buttons.slint`, itself or
|
|
444
|
-
* through the plugin's own Slint files (relative paths, any depth, inside
|
|
445
|
-
* the plugin directory). Local re-exports count as such files too:
|
|
446
|
-
* `export { Row } from "row.slint"` and `export * from "row.slint"` are
|
|
447
|
-
* followed, and `export { BarButtons } from "@vestra/bar-buttons.slint"` (or
|
|
448
|
-
* `export * from` it) in a file the bar imports counts as placing it.
|
|
449
|
-
* Comments do not count.
|
|
450
|
-
*/
|
|
451
|
-
async function importsBarButtons(target, file, seen = new Set()) {
|
|
452
|
-
const path = resolve(target.directory, file);
|
|
453
|
-
if (seen.has(path) || !inside(target.directory, path)) return false;
|
|
454
|
-
seen.add(path);
|
|
455
|
-
const text = await readFile(path, "utf8").catch(() => null);
|
|
456
|
-
if (text === null) return false;
|
|
457
|
-
const code = slintCode(text);
|
|
458
|
-
// { names (or "*"), from }: imports, and re-exports with a source file.
|
|
459
|
-
const imports = [
|
|
460
|
-
...code.matchAll(
|
|
461
|
-
/\b(?:import|export)\s*(?:(\*)\s*from\s*|\{([^}]*)\}\s*from\s*)?"([^"]+)"/g,
|
|
462
|
-
),
|
|
463
|
-
].map(([, star, names, from]) => ({ names: star ?? names ?? "", from }));
|
|
464
|
-
for (const { names, from } of imports)
|
|
465
|
-
if (
|
|
466
|
-
from === "@vestra/bar-buttons.slint" &&
|
|
467
|
-
(names === "*" ||
|
|
468
|
-
names
|
|
469
|
-
.split(",")
|
|
470
|
-
.some((name) => /^BarButtons(\s+as\s+[\w-]+)?$/.test(name.trim())))
|
|
471
|
-
)
|
|
472
|
-
return true;
|
|
473
|
-
for (const { from } of imports) {
|
|
474
|
-
if (
|
|
475
|
-
from.startsWith("@") ||
|
|
476
|
-
!from.endsWith(".slint") ||
|
|
477
|
-
from === "std-widgets.slint"
|
|
478
|
-
)
|
|
479
|
-
continue;
|
|
480
|
-
if (await importsBarButtons(target, join(dirname(file), from), seen))
|
|
481
|
-
return true;
|
|
482
|
-
}
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* A plugin that mounts a bottom bar (`panels.mount("bottom", "<file>")`)
|
|
488
|
-
* whose Slint file does not import `BarButtons` (`@vestra/bar-buttons.slint`),
|
|
489
|
-
* itself or through its local imports: while it is the bottom bar, no
|
|
490
|
-
* plugin's command button shows (SDK 1.5.29). The mount is read from the
|
|
491
|
-
* source text: a literal file name in the call.
|
|
492
|
-
*/
|
|
493
|
-
async function bottomBarWarnings(target) {
|
|
494
|
-
const { typescript: sources } = await sourceFiles(target);
|
|
495
|
-
const files = new Set();
|
|
496
|
-
for (const file of sources) {
|
|
497
|
-
const text = await readFile(join(target.directory, file), "utf8");
|
|
498
|
-
for (const match of text.matchAll(
|
|
499
|
-
/\.mount\(\s*["'`]bottom["'`]\s*,\s*["'`]([^"'`$]+)["'`]/g,
|
|
500
|
-
))
|
|
501
|
-
files.add(match[1]);
|
|
502
|
-
}
|
|
503
|
-
const warnings = [];
|
|
504
|
-
for (const file of files) {
|
|
505
|
-
if (!present(join(target.directory, file))) continue;
|
|
506
|
-
if (await importsBarButtons(target, file)) continue;
|
|
507
|
-
warnings.push(
|
|
508
|
-
`${file} is a bottom bar without BarButtons (import { BarButtons } from "@vestra/bar-buttons.slint"): while it is mounted, the command buttons of every plugin (and the player's sidebar toggle) show nowhere; see docs/guidelines/interface.md#writing-a-bottom-bar`,
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
return warnings;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
431
|
/**
|
|
515
432
|
* A `for` over an array the plugin binds (an `in` or `in-out` property of
|
|
516
433
|
* array type of a component or global in the same file), outside a
|
package/dist/api-requests.js
CHANGED
|
@@ -71,8 +71,12 @@ export const API_REQUESTS = Object.freeze({
|
|
|
71
71
|
"Subtitles.remove": [{ method: "subtitles.remove" }, readsSubtitles],
|
|
72
72
|
"Subtitles.setStyle": [{ method: "subtitles.style" }, readsSubtitles],
|
|
73
73
|
"Timeline.marks": [{ method: "timeline.marks" }],
|
|
74
|
+
"Timeline.ranges": [{ method: "timeline.marks" }],
|
|
74
75
|
"Timeline.onChange": [listen("timeline.changed")],
|
|
75
|
-
"Timeline.layer": [
|
|
76
|
+
"Timeline.layer": [
|
|
77
|
+
{ method: "timeline.set" },
|
|
78
|
+
{ method: "timeline.setRanges" },
|
|
79
|
+
],
|
|
76
80
|
"MainWindow.onChange": [listen("window.state")],
|
|
77
81
|
"MainWindow.setVideoArea": [{ method: "window.setVideoArea" }],
|
|
78
82
|
"MainWindow.setAlwaysOnTop": [{ method: "window.setAlwaysOnTop" }],
|
package/dist/commands.d.ts
CHANGED
|
@@ -158,12 +158,11 @@ export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back"
|
|
|
158
158
|
/**
|
|
159
159
|
* The player's limits on bottom-bar buttons (`CommandOptions.button`), the
|
|
160
160
|
* same values as the player's (its tests check that they agree). A button
|
|
161
|
-
* past `perPlugin`
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* then the earlier declaration, goes first).
|
|
161
|
+
* past `perPlugin` has none (its command still registers and its menu entry
|
|
162
|
+
* stays). `total` is what the strip holds: the `total` lowest by order,
|
|
163
|
+
* plugin id and declaration order; the rest are in the strip's » menu, in
|
|
164
|
+
* the same order (older players keep the `total` highest and leave the
|
|
165
|
+
* others out).
|
|
167
166
|
*
|
|
168
167
|
* @since 1.5.29
|
|
169
168
|
*
|
|
@@ -172,7 +171,7 @@ export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back"
|
|
|
172
171
|
export declare const BAR_BUTTON_LIMITS: {
|
|
173
172
|
/** Buttons one plugin shows (1). */
|
|
174
173
|
readonly perPlugin: 1;
|
|
175
|
-
/** Buttons the
|
|
174
|
+
/** Buttons in the strip, every plugin together, besides the player's own (8); the rest are in its » menu. */
|
|
176
175
|
readonly total: 8;
|
|
177
176
|
/** The highest `order` (999; the lowest is 0). */
|
|
178
177
|
readonly maxOrder: 999;
|
package/dist/commands.js
CHANGED
|
@@ -202,12 +202,11 @@ export const BAR_BUTTON_ICONS = [
|
|
|
202
202
|
/**
|
|
203
203
|
* The player's limits on bottom-bar buttons (`CommandOptions.button`), the
|
|
204
204
|
* same values as the player's (its tests check that they agree). A button
|
|
205
|
-
* past `perPlugin`
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* then the earlier declaration, goes first).
|
|
205
|
+
* past `perPlugin` has none (its command still registers and its menu entry
|
|
206
|
+
* stays). `total` is what the strip holds: the `total` lowest by order,
|
|
207
|
+
* plugin id and declaration order; the rest are in the strip's » menu, in
|
|
208
|
+
* the same order (older players keep the `total` highest and leave the
|
|
209
|
+
* others out).
|
|
211
210
|
*
|
|
212
211
|
* @since 1.5.29
|
|
213
212
|
*
|
|
@@ -216,7 +215,7 @@ export const BAR_BUTTON_ICONS = [
|
|
|
216
215
|
export const BAR_BUTTON_LIMITS = {
|
|
217
216
|
/** Buttons one plugin shows (1). */
|
|
218
217
|
perPlugin: 1,
|
|
219
|
-
/** Buttons the
|
|
218
|
+
/** Buttons in the strip, every plugin together, besides the player's own (8); the rest are in its » menu. */
|
|
220
219
|
total: 8,
|
|
221
220
|
/** The highest `order` (999; the lowest is 0). */
|
|
222
221
|
maxOrder: 999,
|
package/dist/docs-tree.json.gz
CHANGED
|
Binary file
|
|
@@ -300,7 +300,10 @@ export const HOST_CAPABILITIES = {
|
|
|
300
300
|
},
|
|
301
301
|
{
|
|
302
302
|
label: "Show marks on the progress bar",
|
|
303
|
-
methods: [
|
|
303
|
+
methods: [
|
|
304
|
+
{ name: "timeline.set", needs: "timeline.marks" },
|
|
305
|
+
{ name: "timeline.setRanges", needs: "timeline.marks" },
|
|
306
|
+
],
|
|
304
307
|
name: "timeline.marks",
|
|
305
308
|
requires: [],
|
|
306
309
|
residency: "live",
|
package/dist/player-runtime.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { HOST_CAPABILITIES } from "./host-capabilities.ts";
|
|
|
16
16
|
export { BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "./commands.ts";
|
|
17
17
|
export { BINDING_INPUTS } from "./plugin/bindings.ts";
|
|
18
18
|
export { OSD_LIMITS } from "./plugin/osd.ts";
|
|
19
|
+
export { TIMELINE_LIMITS } from "./plugin/timeline.ts";
|
|
19
20
|
export { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./plugin/services.ts";
|
|
20
21
|
export { SUBTITLE_LIMITS } from "./plugin/subtitles.ts";
|
|
21
22
|
export { VIDEO_PROCESSING_LIMITS } from "./processing.ts";
|
package/dist/player-runtime.js
CHANGED
|
@@ -14,6 +14,7 @@ export { HOST_CAPABILITIES } from "./host-capabilities.js";
|
|
|
14
14
|
export { BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "./commands.js";
|
|
15
15
|
export { BINDING_INPUTS } from "./plugin/bindings.js";
|
|
16
16
|
export { OSD_LIMITS } from "./plugin/osd.js";
|
|
17
|
+
export { TIMELINE_LIMITS } from "./plugin/timeline.js";
|
|
17
18
|
export { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./plugin/services.js";
|
|
18
19
|
export { SUBTITLE_LIMITS } from "./plugin/subtitles.js";
|
|
19
20
|
export { VIDEO_PROCESSING_LIMITS } from "./processing.js";
|
package/dist/plugin/osd.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export declare const OSD_LIMITS: Readonly<{
|
|
|
46
46
|
readonly minColumns: 8;
|
|
47
47
|
/** The largest `OsdPanelOptions.maxColumns` (1000). */
|
|
48
48
|
readonly maxColumns: 1000;
|
|
49
|
+
/** Action buttons of one `osd.show` message (`OsdShowOptions.actions`; player 0.7.6) (2). */
|
|
50
|
+
readonly actions: 2;
|
|
51
|
+
/** Characters of an action's title, as shown (40; at least 1 that is not white space). */
|
|
52
|
+
readonly actionChars: 40;
|
|
49
53
|
}>;
|
|
50
54
|
/**
|
|
51
55
|
* Copy and check template arguments: an array or object of at most 16
|
package/dist/plugin/osd.js
CHANGED
|
@@ -47,7 +47,32 @@ export const OSD_LIMITS = Object.freeze({
|
|
|
47
47
|
minColumns: 8,
|
|
48
48
|
/** The largest `OsdPanelOptions.maxColumns` (1000). */
|
|
49
49
|
maxColumns: 1000,
|
|
50
|
+
/** Action buttons of one `osd.show` message (`OsdShowOptions.actions`; player 0.7.6) (2). */
|
|
51
|
+
actions: 2,
|
|
52
|
+
/** Characters of an action's title, as shown (40; at least 1 that is not white space). */
|
|
53
|
+
actionChars: 40,
|
|
50
54
|
});
|
|
55
|
+
/** Copy and check `OsdShowOptions.actions`: what the SDK can check before the player does. */
|
|
56
|
+
function osdActions(options) {
|
|
57
|
+
if (options === undefined || options === null)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (typeof options !== "object")
|
|
60
|
+
throw failure("invalid", "options is an object");
|
|
61
|
+
const actions = options.actions;
|
|
62
|
+
if (actions === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!Array.isArray(actions) || actions.length > OSD_LIMITS.actions)
|
|
65
|
+
throw failure("invalid", `actions is a list of at most ${OSD_LIMITS.actions}`);
|
|
66
|
+
return actions.map((action, i) => {
|
|
67
|
+
const { title, command } = (action ?? {});
|
|
68
|
+
if (typeof command !== "string" || command === "")
|
|
69
|
+
throw failure("invalid", `actions[${i}].command is a command id`);
|
|
70
|
+
return {
|
|
71
|
+
title: shownText(title, undefined, `actions[${i}].title`),
|
|
72
|
+
command,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
51
76
|
/**
|
|
52
77
|
* Copy and check template arguments: an array or object of at most 16
|
|
53
78
|
* strings, finite numbers, texts and lists (`TEXT_LIMITS`).
|
|
@@ -134,6 +159,6 @@ class PanelHandle {
|
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
161
|
export const osd = (scope) => ({
|
|
137
|
-
show: async (text, args) => scope.runtime.host.osd.show(shownText(text, args), templateArgs(args)),
|
|
162
|
+
show: async (text, args, options) => scope.runtime.host.osd.show(shownText(text, args), templateArgs(args), osdActions(options)),
|
|
138
163
|
panel: () => new PanelHandle(scope),
|
|
139
164
|
});
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { BuiltinCommand, BuiltinSubmenu } from "../commands.ts";
|
|
|
7
7
|
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.ts";
|
|
8
8
|
import type { CapabilityInfo } from "../capabilities.ts";
|
|
9
9
|
import { OSD_LIMITS } from "./osd.ts";
|
|
10
|
+
import { TIMELINE_LIMITS } from "./timeline.ts";
|
|
10
11
|
import { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./services.ts";
|
|
11
12
|
import { SUBTITLE_LIMITS } from "./subtitles.ts";
|
|
12
13
|
import { BINDING_INPUTS } from "./bindings.ts";
|
|
@@ -36,6 +37,8 @@ export type { CapabilityInfo };
|
|
|
36
37
|
export { MESSAGE_LIMIT };
|
|
37
38
|
/** The player's limits on the on-screen display. */
|
|
38
39
|
export { OSD_LIMITS };
|
|
40
|
+
/** The player's limits on a timeline layer. */
|
|
41
|
+
export { TIMELINE_LIMITS };
|
|
39
42
|
/** The player's limits on stored secrets. */
|
|
40
43
|
export { SECRET_LIMITS };
|
|
41
44
|
/** The player's limits on `plugin.storage.session`. */
|
|
@@ -647,6 +650,43 @@ export declare class TestHost implements Transport {
|
|
|
647
650
|
* template and args the plugin sent are in `host.sent("osd.show")`.
|
|
648
651
|
*/
|
|
649
652
|
readonly osd: string[];
|
|
653
|
+
/**
|
|
654
|
+
* The action buttons of each `osd.show` message, in step with `osd`
|
|
655
|
+
* (index for index): each button's title as shown and its command; `[]`
|
|
656
|
+
* for a message without actions (SDK 1.6.9).
|
|
657
|
+
*/
|
|
658
|
+
readonly osdActions: {
|
|
659
|
+
/** The button title as shown, in the plugin's locale. */
|
|
660
|
+
title: string;
|
|
661
|
+
/** The local id of the plugin's command the button runs. */
|
|
662
|
+
command: string;
|
|
663
|
+
}[][];
|
|
664
|
+
/**
|
|
665
|
+
* Press button `index` of the newest OSD message, as the user does
|
|
666
|
+
* (player 0.7.6): its command runs as if picked from a menu, and settles.
|
|
667
|
+
* A disabled or busy command does nothing, as in the player.
|
|
668
|
+
*
|
|
669
|
+
* @param index - 0 or 1.
|
|
670
|
+
*
|
|
671
|
+
* @throws `Error` (rejects) The newest message has no button `index`.
|
|
672
|
+
*
|
|
673
|
+
* @example Press "Show in Folder" on the saved message
|
|
674
|
+
* The test checks the button the plugin put on its newest message, then
|
|
675
|
+
* presses it as the user would: the plugin's `reveal` command runs.
|
|
676
|
+
* ```ts
|
|
677
|
+
* import assert from "node:assert/strict";
|
|
678
|
+
* declare const host: TestHost;
|
|
679
|
+
* // ---cut---
|
|
680
|
+
* assert.deepEqual(host.osdActions.at(-1), [
|
|
681
|
+
* { title: "Show in Folder", command: "reveal" },
|
|
682
|
+
* ]);
|
|
683
|
+
* await host.pressOsdAction(0); // runs the plugin's `reveal` command
|
|
684
|
+
* ```
|
|
685
|
+
*
|
|
686
|
+
* @capability None.
|
|
687
|
+
* @since 1.6.9
|
|
688
|
+
*/
|
|
689
|
+
pressOsdAction(index: number): Promise<void>;
|
|
650
690
|
/** The text of this plugin's osd panel as shown, `null` when hidden. */
|
|
651
691
|
osdPanel: string | null;
|
|
652
692
|
/**
|
|
@@ -948,7 +988,65 @@ export declare class TestHost implements Transport {
|
|
|
948
988
|
/** The mark's title, `null` without one. */
|
|
949
989
|
title: string | null;
|
|
950
990
|
}[];
|
|
991
|
+
/**
|
|
992
|
+
* Each plugin's ranges by plugin id; layers without ranges are absent
|
|
993
|
+
* (SDK 1.6.9).
|
|
994
|
+
*/
|
|
995
|
+
rangeLayers: Map<string, {
|
|
996
|
+
/** Seconds from the start. */
|
|
997
|
+
start: number;
|
|
998
|
+
/** Seconds from the start, after `start`. */
|
|
999
|
+
end: number;
|
|
1000
|
+
/** `"selection"` or `"region"`. */
|
|
1001
|
+
style: string;
|
|
1002
|
+
/** The range's title, `null` without one. */
|
|
1003
|
+
title: string | null;
|
|
1004
|
+
}[]>;
|
|
1005
|
+
/** Every layer's ranges merged, sorted by start, with the plugin's id. */
|
|
1006
|
+
readonly ranges: {
|
|
1007
|
+
/** The manifest id of the plugin that published the range. */
|
|
1008
|
+
plugin: string;
|
|
1009
|
+
/** Seconds from the start. */
|
|
1010
|
+
start: number;
|
|
1011
|
+
/** Seconds from the start, after `start`. */
|
|
1012
|
+
end: number;
|
|
1013
|
+
/** `"selection"` or `"region"`. */
|
|
1014
|
+
style: string;
|
|
1015
|
+
/** The range's title, `null` without one. */
|
|
1016
|
+
title: string | null;
|
|
1017
|
+
}[];
|
|
951
1018
|
};
|
|
1019
|
+
/**
|
|
1020
|
+
* Simulate another plugin publishing its timeline ranges (or clearing
|
|
1021
|
+
* them with `[]`); pushes `timeline.changed` when the plugin subscribed.
|
|
1022
|
+
*
|
|
1023
|
+
* @param pluginId - The other plugin's manifest id.
|
|
1024
|
+
* @param ranges - All its ranges.
|
|
1025
|
+
*
|
|
1026
|
+
* @example Let another plugin publish a clip range
|
|
1027
|
+
* The plugin under test sees a range another plugin put on the seek bar:
|
|
1028
|
+
* `setTimelineRanges` stores that plugin's ranges and pushes
|
|
1029
|
+
* `timeline.changed`, and `settle()` lets it arrive.
|
|
1030
|
+
* ```ts
|
|
1031
|
+
* import assert from "node:assert/strict";
|
|
1032
|
+
* declare const host: TestHost;
|
|
1033
|
+
* // ---cut---
|
|
1034
|
+
* host.setTimelineRanges("community.clips", [
|
|
1035
|
+
* { start: 40, end: 52.5, style: "selection", title: "Clip" },
|
|
1036
|
+
* ]);
|
|
1037
|
+
* await host.settle();
|
|
1038
|
+
* assert.equal(plugin.timeline.ranges[0]?.plugin, "community.clips");
|
|
1039
|
+
* ```
|
|
1040
|
+
*
|
|
1041
|
+
* @capability None.
|
|
1042
|
+
* @since 1.6.9
|
|
1043
|
+
*/
|
|
1044
|
+
setTimelineRanges(pluginId: string, ranges: {
|
|
1045
|
+
start: number;
|
|
1046
|
+
end: number;
|
|
1047
|
+
style: string;
|
|
1048
|
+
title?: string | null;
|
|
1049
|
+
}[]): void;
|
|
952
1050
|
/**
|
|
953
1051
|
* Simulate another plugin publishing its timeline layer (or clearing it
|
|
954
1052
|
* with `[]`); pushes `timeline.changed` when the plugin subscribed.
|
|
@@ -2645,8 +2743,8 @@ export declare class TestHost implements Transport {
|
|
|
2645
2743
|
* The plugin's bottom-bar buttons (`CommandOptions.button`) as the player
|
|
2646
2744
|
* shows them: at most one, the first declared (`BAR_BUTTON_LIMITS`; a
|
|
2647
2745
|
* later one stays a menu item only). Empty once the command is disposed.
|
|
2648
|
-
* Whether the
|
|
2649
|
-
* the
|
|
2746
|
+
* Whether one is in the strip or its » menu is the player's business
|
|
2747
|
+
* (the room and the other plugins' buttons).
|
|
2650
2748
|
*
|
|
2651
2749
|
* @example Click the plugin's bottom-bar button in a test
|
|
2652
2750
|
* The test finds the plugin's Store button, clicks it and checks that the
|