@vestra-player/sdk 1.5.4 → 1.5.7
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 +26 -0
- package/README.md +3 -1
- package/dist/plugin/stream.js +62 -4
- package/dist/plugin/testing.d.ts +23 -0
- package/dist/plugin/testing.js +44 -1
- package/dist/plugin/types.d.ts +34 -8
- package/llms-full.txt +7119 -0
- package/llms.txt +205 -0
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.7
|
|
4
|
+
|
|
5
|
+
**Replaced or closed streams stop with `null`.** A fix; no API change, `apiVersion` stays `vestra/1`, any player.
|
|
6
|
+
|
|
7
|
+
- **`Stream.onStop(null)` when another source replaced the stream or it was closed**, as documented. The player retires a stream's producer at once in both cases, and the SDK then passed the player's answer (`unavailable`, "producer absent") to `onStop` as an error. The SDK now reads the player's state to tell the two apart: replaced or closed is `null`; a failure is a `stream` error with the player's text (also when the player retired the producer right after failing it, which used to give "producer absent" too).
|
|
8
|
+
- The same in the start-up paths: `openStream` replaced or closed before it ran rejects with `replaced` (was `unavailable`); a file resolver's stream replaced after the player selected it is a cancellation and no longer reaches `onError`.
|
|
9
|
+
- **A file resolver's stream that cannot start after `resolve` returned** fails the open with Chinese text instead of the SDK's English (`"audio device is not ready: deviceLost"`): `device` "音频输出设备不可用", `timeout` "启动超时", `busy` "播放器正忙,请稍后重试", other start-up failures "无法启动播放"; the English detail goes to the plugin's log. The plugin never saw these errors, so the user read the SDK's words. An error thrown by `frame()` keeps its message; text for a file the plugin can judge (a bad header) belongs in `resolve`.
|
|
10
|
+
- **`TestHost` retires producers like the player**: on `open`, `close`, another `openProvider` or resolved stream, a retired producer's `producer.state` answers `unavailable` and `producer.ack` `stale`. New `FakeProducer.retired`, and **`host.failStream(producerId, message)`** to fail the playing stream the way the player does (`phase: "error"` with `message`, producer retired). Tests that replaced a stream used to pass where the player failed.
|
|
11
|
+
|
|
12
|
+
## 1.5.6
|
|
13
|
+
|
|
14
|
+
**A reference for AI assistants.** Documentation and packaging only; no API change, `apiVersion` stays `vestra/1`. (1.5.5 was not published to npm; this release includes its changes.)
|
|
15
|
+
|
|
16
|
+
- **`llms.txt` and `llms-full.txt`** ([llmstxt.org](https://llmstxt.org)) in the package, exported as `@vestra-player/sdk/llms.txt` and `@vestra-player/sdk/llms-full.txt` (in `node_modules/@vestra-player/sdk/`), and on the documentation site (`https://vestrasys.github.io/vestra-player-sdk/llms.txt`). For Vestra Player AI and other assistants that write plugins.
|
|
17
|
+
- `llms.txt` (about 10 KB): what a plugin is, the rules, the folder layout, a manifest with every field and the manifest limits, `activate`, every capability, a `TestHost` test, the command line, the common error codes, and the line numbers of each topic in the full file.
|
|
18
|
+
- `llms-full.txt` (about 300 KB): the guides (Getting started, the UI concept page, both how-tos), every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its description and its `@capability`, `@player`, `@param`, `@returns`, `@throws` and `@deprecated` tags, the internal types the API refers to, and each example's README, walkthrough and complete source (TypeScript, Slint, tests).
|
|
19
|
+
- Generated from the sources by `npm run docs:llms` (`scripts/llms.mjs`); CI fails when the committed files are stale.
|
|
20
|
+
- **Getting started**: a "Rules for plugins" section (only the public SDK, `import type`, no other plugins, no system commands, macOS and Windows, your own id) and a manifest with every field.
|
|
21
|
+
- `scripts/check-pack.mjs` allows the two files and checks that they name the package version; the install smoke test resolves both exports.
|
|
22
|
+
|
|
23
|
+
## 1.5.5
|
|
24
|
+
|
|
25
|
+
**Reserved `vestra.` ids** (player 0.4.1). Documentation only; no API change, `apiVersion` stays `vestra/1`.
|
|
26
|
+
|
|
27
|
+
- Only the plugins built into the player are official plugins; the ids `vestra` and `vestra.*` (any case) are reserved for them. Every other plugin is a third-party plugin, including those hosted in the official plugin repository, and uses `<GitHub user or organization>.<plugin name>`.
|
|
28
|
+
- `PluginError` lists the code `reserved`: `packages.validate`, `packages.validateArchive`, `PackageCandidate.trial` and `PackageCandidate.install` refuse a package with a reserved id. Development loads (`vestra-player --plugin DIR`) still accept one, with a warning, so built-in plugins can be developed; they are never installed.
|
|
3
29
|
## 1.5.4
|
|
4
30
|
|
|
5
31
|
**Markdown in list rows** (the player after 0.4.0). Additive; `apiVersion` stays `vestra/1`, and plugins written for 1.5.3 need no change.
|
package/README.md
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
<a href="https://vestrasys.github.io/vestra-player-sdk/">Documentation</a> ·
|
|
13
13
|
<a href="https://vestrasys.github.io/vestra-player-sdk/documents/Getting_started.html">Getting started</a> ·
|
|
14
14
|
<a href="https://github.com/vestrasys/vestra-player-sdk/tree/main/examples">Examples</a> ·
|
|
15
|
-
<a href="https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md">Changelog</a>
|
|
15
|
+
<a href="https://github.com/vestrasys/vestra-player-sdk/blob/main/CHANGELOG.md">Changelog</a> ·
|
|
16
|
+
<a href="https://vestrasys.github.io/vestra-player-sdk/llms.txt">llms.txt</a>
|
|
16
17
|
</p>
|
|
17
18
|
## Features
|
|
18
19
|
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
- **Tests without the player**: `@vestra-player/sdk/testing` runs your plugin against an in-memory host with `node --test`.
|
|
22
23
|
- **One command line tool**, `vestra-plugin`, to check, test, build and pack a `.vxp` package.
|
|
23
24
|
- **The same API on macOS and Windows**; every plugin runs in its own Node.js 26 process that the player ships.
|
|
25
|
+
- **A reference for AI assistants**: [`llms.txt`](https://vestrasys.github.io/vestra-player-sdk/llms.txt) (short index) and [`llms-full.txt`](https://vestrasys.github.io/vestra-player-sdk/llms-full.txt) (every declaration, the guides and the examples), also in the package as `@vestra-player/sdk/llms.txt` and `@vestra-player/sdk/llms-full.txt`.
|
|
24
26
|
|
|
25
27
|
## Install
|
|
26
28
|
|
package/dist/plugin/stream.js
CHANGED
|
@@ -79,7 +79,8 @@ class StreamHandle {
|
|
|
79
79
|
}, 5);
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
|
|
82
|
+
const retired = await retirement(this.#service, this.#generation, error);
|
|
83
|
+
void this.#end(retired === undefined ? toError(error) : retired);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
async #render() {
|
|
@@ -131,6 +132,28 @@ class StreamHandle {
|
|
|
131
132
|
await this.stop().catch(() => { });
|
|
132
133
|
}
|
|
133
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The player retires a producer the moment another source replaces it or
|
|
137
|
+
* the source closes, and after it failed, so its `producer.state` then
|
|
138
|
+
* answers `unavailable` (and `producer.ack` `stale`). The player's state
|
|
139
|
+
* says which: `null` (replaced or closed), the failure with the player's
|
|
140
|
+
* words, or `undefined` when the error is not a retirement.
|
|
141
|
+
*/
|
|
142
|
+
async function retirement(service, generation, error) {
|
|
143
|
+
const code = error?.code;
|
|
144
|
+
if (code !== "unavailable" && code !== "stale")
|
|
145
|
+
return undefined;
|
|
146
|
+
const readable = service.runtime.can("playback.read");
|
|
147
|
+
if (readable)
|
|
148
|
+
await service.refresh().catch(() => { });
|
|
149
|
+
const raw = service.raw;
|
|
150
|
+
if (raw.stamp.generation !== generation || raw.phase === "empty")
|
|
151
|
+
return null;
|
|
152
|
+
if (raw.phase === "error")
|
|
153
|
+
return failure("stream", service.state.error ?? "无法打开此媒体");
|
|
154
|
+
// Without `playback.read` the state may be behind: no failure was seen.
|
|
155
|
+
return readable ? undefined : null;
|
|
156
|
+
}
|
|
134
157
|
/** Start-up timeouts say which step ran out of time. */
|
|
135
158
|
function timeout(stage, message) {
|
|
136
159
|
return Object.assign(failure("timeout", message), { stage });
|
|
@@ -303,10 +326,40 @@ export async function openStream(scope, service, options) {
|
|
|
303
326
|
// An abort can arrive before the host answered the selecting request: wait
|
|
304
327
|
// for that answer so a source selected afterwards is still closed.
|
|
305
328
|
await opened?.catch(() => { });
|
|
329
|
+
// Read before closing: replaced during activation is not a failure.
|
|
330
|
+
const retired = generation === undefined
|
|
331
|
+
? undefined
|
|
332
|
+
: await retirement(service, generation, error);
|
|
306
333
|
await abandon(service, p, generation);
|
|
307
|
-
|
|
334
|
+
if (retired === null)
|
|
335
|
+
throw failure("replaced", "另一个来源已替换此媒体");
|
|
336
|
+
throw retired ?? error;
|
|
308
337
|
}
|
|
309
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* What the user reads when a file resolver's stream cannot start. The
|
|
341
|
+
* player shows the answer as the open's failure, and the plugin never sees
|
|
342
|
+
* these errors: they happen after `resolve` returned. The SDK's and the
|
|
343
|
+
* player's own codes get the player's words; the detail goes to the log.
|
|
344
|
+
* An error the plugin threw from `frame()` keeps its own words.
|
|
345
|
+
*/
|
|
346
|
+
const START_TEXT = {
|
|
347
|
+
device: "音频输出设备不可用",
|
|
348
|
+
timeout: "启动超时",
|
|
349
|
+
busy: "播放器正忙,请稍后重试",
|
|
350
|
+
stream: "无法启动播放",
|
|
351
|
+
producer: "无法启动播放",
|
|
352
|
+
unavailable: "无法启动播放",
|
|
353
|
+
invalid: "无法启动播放",
|
|
354
|
+
};
|
|
355
|
+
function startFailure(scope, error) {
|
|
356
|
+
const code = error?.code;
|
|
357
|
+
const text = code === undefined ? undefined : START_TEXT[code];
|
|
358
|
+
if (text === undefined)
|
|
359
|
+
return error;
|
|
360
|
+
console.error(`[${scope.id}] file resolver stream did not start:`, error.message);
|
|
361
|
+
return Object.assign(failure(code, text), { cause: error });
|
|
362
|
+
}
|
|
310
363
|
/**
|
|
311
364
|
* A file resolver's stream (`ResolvedStream`): prepared and prerolled while
|
|
312
365
|
* the open waits, then handed to the host as the answer. Once the host
|
|
@@ -320,6 +373,8 @@ export async function resolvedStream(scope, service, stream, signal) {
|
|
|
320
373
|
const p = await prepare(host, stream, () => {
|
|
321
374
|
if (signal.aborted || scope.disposed)
|
|
322
375
|
throw abortError();
|
|
376
|
+
}).catch((error) => {
|
|
377
|
+
throw startFailure(scope, error);
|
|
323
378
|
});
|
|
324
379
|
return {
|
|
325
380
|
kind: "stream",
|
|
@@ -336,9 +391,12 @@ export async function resolvedStream(scope, service, stream, signal) {
|
|
|
336
391
|
throw abortError();
|
|
337
392
|
}
|
|
338
393
|
catch (error) {
|
|
394
|
+
// Replaced or closed before it ran is a cancellation, not a failure.
|
|
395
|
+
const retired = await retirement(service, stamp.generation, error);
|
|
339
396
|
await abandon(service, p, stamp.generation);
|
|
340
|
-
|
|
341
|
-
|
|
397
|
+
const reported = retired === undefined ? error : retired;
|
|
398
|
+
if (reported && reported.code !== "aborted")
|
|
399
|
+
scope.report(reported);
|
|
342
400
|
return;
|
|
343
401
|
}
|
|
344
402
|
const handle = handleOf(scope, service, p, stamp.generation);
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -117,6 +117,13 @@ export interface FakeProducer {
|
|
|
117
117
|
videoFrames: number;
|
|
118
118
|
/** `true` once the plugin released the producer (stream stopped or disposed). */
|
|
119
119
|
released: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* `true` once the player retired the producer, as the real player does at
|
|
122
|
+
* once: another source replaced it (`open`, another stream, a resolved
|
|
123
|
+
* file), the source was closed, or {@link TestHost.failStream} failed it.
|
|
124
|
+
* A retired producer's `producer.state` answers `unavailable`.
|
|
125
|
+
*/
|
|
126
|
+
retired: boolean;
|
|
120
127
|
/**
|
|
121
128
|
* `true` if the frame connection closed while this stream was still the
|
|
122
129
|
* player's source: the player reports that as a playback failure
|
|
@@ -1025,6 +1032,22 @@ export declare class TestHost implements Transport {
|
|
|
1025
1032
|
* ```
|
|
1026
1033
|
*/
|
|
1027
1034
|
openElsewhere(source: string, phase?: SourceState["phase"]): void;
|
|
1035
|
+
/**
|
|
1036
|
+
* Simulate a generated stream failing in the player (its audio device
|
|
1037
|
+
* lost, its frame connection broken), like the player: the source fails
|
|
1038
|
+
* with `message` (`phase: "error"`) and the producer is retired at once.
|
|
1039
|
+
* The stream's `onStop` listeners get a `stream` error with `message`.
|
|
1040
|
+
*
|
|
1041
|
+
* @param producerId - The producer, from {@link TestHost.producers}.
|
|
1042
|
+
* @param message - The player's failure text.
|
|
1043
|
+
*
|
|
1044
|
+
* @example
|
|
1045
|
+
* ```ts
|
|
1046
|
+
* const [producer] = host.producers.values();
|
|
1047
|
+
* host.failStream(producer.id, "producer transport closed");
|
|
1048
|
+
* ```
|
|
1049
|
+
*/
|
|
1050
|
+
failStream(producerId: number, message: string): void;
|
|
1028
1051
|
/**
|
|
1029
1052
|
* Press a binding like the host's table would: panel-focus bindings first
|
|
1030
1053
|
* when `panel` is true, then global ones; the earliest registration wins a
|
package/dist/plugin/testing.js
CHANGED
|
@@ -1385,6 +1385,31 @@ export class TestHost {
|
|
|
1385
1385
|
if (this.#resolve(source, false) === null)
|
|
1386
1386
|
this.setSource({ phase });
|
|
1387
1387
|
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Simulate a generated stream failing in the player (its audio device
|
|
1390
|
+
* lost, its frame connection broken), like the player: the source fails
|
|
1391
|
+
* with `message` (`phase: "error"`) and the producer is retired at once.
|
|
1392
|
+
* The stream's `onStop` listeners get a `stream` error with `message`.
|
|
1393
|
+
*
|
|
1394
|
+
* @param producerId - The producer, from {@link TestHost.producers}.
|
|
1395
|
+
* @param message - The player's failure text.
|
|
1396
|
+
*
|
|
1397
|
+
* @example
|
|
1398
|
+
* ```ts
|
|
1399
|
+
* const [producer] = host.producers.values();
|
|
1400
|
+
* host.failStream(producer.id, "producer transport closed");
|
|
1401
|
+
* ```
|
|
1402
|
+
*/
|
|
1403
|
+
failStream(producerId, message) {
|
|
1404
|
+
const producer = this.producers.get(producerId);
|
|
1405
|
+
if (!producer || !this.#current(producerId))
|
|
1406
|
+
throw new Error(`producer ${producerId} is not the player's source`);
|
|
1407
|
+
this.setSource({
|
|
1408
|
+
phase: "error",
|
|
1409
|
+
terminalReason: { failed: message },
|
|
1410
|
+
});
|
|
1411
|
+
producer.retired = true;
|
|
1412
|
+
}
|
|
1388
1413
|
/**
|
|
1389
1414
|
* Press a binding like the host's table would: panel-focus bindings first
|
|
1390
1415
|
* when `panel` is true, then global ones; the earliest registration wins a
|
|
@@ -1721,6 +1746,10 @@ export class TestHost {
|
|
|
1721
1746
|
if (control.kind === "rate")
|
|
1722
1747
|
next.rate = Number(control.rate);
|
|
1723
1748
|
}
|
|
1749
|
+
// Like the player: the selected producer is retired as soon as another
|
|
1750
|
+
// source replaces it or the source closes.
|
|
1751
|
+
if (["open", "openProvider", "close"].includes(String(intent.kind)))
|
|
1752
|
+
this.#retireSelected(intent.kind === "openProvider" ? Number(intent.producerId) : null);
|
|
1724
1753
|
if (intent.kind === "openProvider")
|
|
1725
1754
|
this.#selected = Number(intent.producerId);
|
|
1726
1755
|
if (intent.kind === "open" || intent.kind === "close")
|
|
@@ -1748,6 +1777,14 @@ export class TestHost {
|
|
|
1748
1777
|
}
|
|
1749
1778
|
return null;
|
|
1750
1779
|
}
|
|
1780
|
+
/** Retire the producer that is the player's source, unless it is `keep`. */
|
|
1781
|
+
#retireSelected(keep) {
|
|
1782
|
+
for (const producer of this.producers.values())
|
|
1783
|
+
if (producer.id !== keep &&
|
|
1784
|
+
!producer.retired &&
|
|
1785
|
+
this.#current(producer.id))
|
|
1786
|
+
producer.retired = true;
|
|
1787
|
+
}
|
|
1751
1788
|
/** Whether producer `id` is the player's source. */
|
|
1752
1789
|
#current(id) {
|
|
1753
1790
|
return (this.source.kind === "producer" &&
|
|
@@ -1853,9 +1890,10 @@ export class TestHost {
|
|
|
1853
1890
|
const producer = this.producers.get(Number(result.producerId));
|
|
1854
1891
|
if (result.kind !== "stream")
|
|
1855
1892
|
fail("invalid resolved stream descriptor");
|
|
1856
|
-
if (!producer || producer.released)
|
|
1893
|
+
if (!producer || producer.released || producer.retired)
|
|
1857
1894
|
fail("unavailable: producer absent");
|
|
1858
1895
|
// Like openProvider, but the source keeps the path and `startPaused`.
|
|
1896
|
+
this.#retireSelected(producer.id);
|
|
1859
1897
|
this.#selected = producer.id;
|
|
1860
1898
|
producer.active = true;
|
|
1861
1899
|
producer.acknowledged = false;
|
|
@@ -2922,6 +2960,7 @@ export class TestHost {
|
|
|
2922
2960
|
receivedFrames: 0,
|
|
2923
2961
|
videoFrames: 0,
|
|
2924
2962
|
released: false,
|
|
2963
|
+
retired: false,
|
|
2925
2964
|
droppedWhileSelected: false,
|
|
2926
2965
|
path,
|
|
2927
2966
|
admitted: !tcp,
|
|
@@ -2988,6 +3027,8 @@ export class TestHost {
|
|
|
2988
3027
|
const producer = this.producers.get(Number(p.producerId));
|
|
2989
3028
|
if (!producer)
|
|
2990
3029
|
throw new HostError("missing", "producer");
|
|
3030
|
+
if (producer.retired || producer.released)
|
|
3031
|
+
throw new HostError("unavailable", "producer absent");
|
|
2991
3032
|
const current = this.#current(producer.id);
|
|
2992
3033
|
return {
|
|
2993
3034
|
input: null,
|
|
@@ -3017,6 +3058,8 @@ export class TestHost {
|
|
|
3017
3058
|
const producer = this.producers.get(Number(control.producerId));
|
|
3018
3059
|
if (!producer)
|
|
3019
3060
|
throw new HostError("missing", "producer");
|
|
3061
|
+
if (producer.retired || producer.released)
|
|
3062
|
+
throw new HostError("stale", "producer control changed");
|
|
3020
3063
|
producer.pending = null;
|
|
3021
3064
|
producer.acknowledged = true;
|
|
3022
3065
|
producer.epoch = Number(control.epoch);
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -75,6 +75,7 @@ export interface Disposable {
|
|
|
75
75
|
* | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
76
76
|
* | `cleanup` | {@link PackageTrial.stop} could not confirm cleanup; call it again. |
|
|
77
77
|
* | `limit` | A player-wide limit was reached (for example the number of commands). |
|
|
78
|
+
* | `reserved` | {@link Packages.validate}, {@link PackageCandidate.trial}, {@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). |
|
|
78
79
|
* | `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
|
|
79
80
|
* | `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. |
|
|
80
81
|
* | `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. |
|
|
@@ -3001,9 +3002,15 @@ export interface Stream extends Disposable {
|
|
|
3001
3002
|
/**
|
|
3002
3003
|
* The stream stopped for a reason outside this plugin: `error` is null when
|
|
3003
3004
|
* another source replaced it or the user closed it, otherwise the failure
|
|
3004
|
-
* (`stream
|
|
3005
|
-
* itself stops it (`stop()`, `dispose()`,
|
|
3006
|
-
* `stop()` is that notification.
|
|
3005
|
+
* (`stream` with the player's text, `invalid` for a bad frame, …). Not
|
|
3006
|
+
* called when the plugin itself stops it (`stop()`, `dispose()`,
|
|
3007
|
+
* deactivation): the promise of `stop()` is that notification.
|
|
3008
|
+
*
|
|
3009
|
+
* The player retires a stream's producer as soon as it is replaced or
|
|
3010
|
+
* closed; the SDK reads the player's state to tell that from a failure
|
|
3011
|
+
* (before 1.5.7 a replaced or closed stream could get `unavailable`
|
|
3012
|
+
* "producer absent"). Without `playback.read` a failure the SDK has not
|
|
3013
|
+
* seen yet is reported as null.
|
|
3007
3014
|
*/
|
|
3008
3015
|
onStop(listener: (error: PluginError | null) => void): Disposable;
|
|
3009
3016
|
/**
|
|
@@ -3146,7 +3153,8 @@ export interface Player {
|
|
|
3146
3153
|
* activation, pausing and acknowledgement. Frames travel over a local
|
|
3147
3154
|
* connection the SDK sets up: a Unix socket on macOS and Linux, loopback
|
|
3148
3155
|
* TCP with a token on Windows. Rejects with a {@link StreamStartError}
|
|
3149
|
-
* (`timeout`), `stream` or `
|
|
3156
|
+
* (`timeout`), `stream`, `device`, or `replaced` when another source
|
|
3157
|
+
* replaced it (or it was closed) before it ran.
|
|
3150
3158
|
*
|
|
3151
3159
|
* @example
|
|
3152
3160
|
* ```ts
|
|
@@ -3936,6 +3944,14 @@ export interface FileResolverOptions {
|
|
|
3936
3944
|
/**
|
|
3937
3945
|
* What a file resolver returns: a stream, with the same frame contract as
|
|
3938
3946
|
* {@link StreamOptions} (size limits, `frame()` on the audio clock).
|
|
3947
|
+
*
|
|
3948
|
+
* After `resolve` returns, the SDK starts the stream while the open waits.
|
|
3949
|
+
* If that fails, the open fails with the player's words instead of the
|
|
3950
|
+
* SDK's (the detail goes to the log): `device` "音频输出设备不可用",
|
|
3951
|
+
* `timeout` "启动超时", `busy` "播放器正忙,请稍后重试", other start-up
|
|
3952
|
+
* failures "无法启动播放". An error thrown by `frame()` keeps its message.
|
|
3953
|
+
* To give your own text for a file you can tell is unusable (a bad header),
|
|
3954
|
+
* check it in `resolve` and throw there.
|
|
3939
3955
|
*/
|
|
3940
3956
|
export interface ResolvedStream {
|
|
3941
3957
|
/** Always `"stream"`. */
|
|
@@ -4083,13 +4099,18 @@ export interface PackageCandidate {
|
|
|
4083
4099
|
readonly manifest: PackageManifest;
|
|
4084
4100
|
/** The checks the player ran. */
|
|
4085
4101
|
readonly checks: string[];
|
|
4086
|
-
/**
|
|
4102
|
+
/**
|
|
4103
|
+
* Load it temporarily alongside the installed plugins. A reserved id
|
|
4104
|
+
* (`vestra.*`) fails with `reserved`.
|
|
4105
|
+
*/
|
|
4087
4106
|
trial(): Promise<PackageTrial>;
|
|
4088
4107
|
/**
|
|
4089
4108
|
* Install it. The same source upgrades the installed plugin (its data
|
|
4090
4109
|
* stays); another source fails with `conflict` unless `replace` is set;
|
|
4091
4110
|
* a built-in id fails with `unsupported` (built-ins update with the
|
|
4092
|
-
* player).
|
|
4111
|
+
* player). A reserved id (`vestra` or `vestra.*`, which every built-in
|
|
4112
|
+
* uses) fails with `reserved` (player 0.4.1; already when validated), as
|
|
4113
|
+
* only the player installs built-in plugins.
|
|
4093
4114
|
*/
|
|
4094
4115
|
install(options?: InstallOptions): Promise<PackageChange>;
|
|
4095
4116
|
}
|
|
@@ -4197,12 +4218,17 @@ export interface PluginWarning {
|
|
|
4197
4218
|
* ```
|
|
4198
4219
|
*/
|
|
4199
4220
|
export interface Packages {
|
|
4200
|
-
/**
|
|
4221
|
+
/**
|
|
4222
|
+
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
4223
|
+
* reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
|
|
4224
|
+
* `reserved` (player 0.4.1).
|
|
4225
|
+
*/
|
|
4201
4226
|
validate(directory: string): Promise<PackageCandidate>;
|
|
4202
4227
|
/**
|
|
4203
4228
|
* Validate a `.vxp` package file (`plugins.manage`, player 0.2.0): the
|
|
4204
4229
|
* same checks as {@link Packages.validate} after unpacking. Check the
|
|
4205
|
-
* file's sha256 yourself before calling when you know it.
|
|
4230
|
+
* file's sha256 yourself before calling when you know it. A reserved id
|
|
4231
|
+
* fails with `reserved`, as in {@link Packages.validate}.
|
|
4206
4232
|
*/
|
|
4207
4233
|
validateArchive(file: string): Promise<PackageCandidate>;
|
|
4208
4234
|
/** Restore a plugin's previous version (`plugins.manage`). */
|