@vestra-player/sdk 1.5.1 → 1.5.3
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 +30 -0
- package/dist/plugin/packages.js +5 -0
- package/dist/plugin/resolvers.js +16 -4
- package/dist/plugin/stream.d.ts +11 -1
- package/dist/plugin/stream.js +142 -68
- package/dist/plugin/testing.d.ts +88 -2
- package/dist/plugin/testing.js +360 -22
- package/dist/plugin/types.d.ts +369 -22
- package/dist/plugin/ui.js +99 -3
- package/dist/source-resolvers.d.ts +33 -6
- package/dist/source-resolvers.js +42 -14
- package/dist/wire.d.ts +20 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ export type ResolvedResource = {
|
|
|
8
8
|
headers?: Record<string, string>;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
* What a resolver returns ({@link
|
|
11
|
+
* What a resolver returns ({@link WebResolverOptions.resolve}): the playable
|
|
12
12
|
* stream(s) behind a page URL. Both streams must use identical `headers`;
|
|
13
13
|
* different maps fail explicitly (nothing is dropped silently).
|
|
14
14
|
*
|
|
@@ -35,21 +35,48 @@ export type ResolvedMedia = {
|
|
|
35
35
|
/** A separate audio stream, when audio and video are split. */
|
|
36
36
|
audio?: ResolvedResource;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* A resolver declaration on the wire: a web resolver (`schemes` + `hosts`)
|
|
40
|
+
* or, from player 0.4.1, a file resolver (`extensions`), never both.
|
|
41
|
+
*/
|
|
38
42
|
export type SourceResolverDeclaration = {
|
|
39
43
|
id: string;
|
|
40
44
|
/** Finite initial contract: http and/or https, exact normalized host names. */
|
|
41
45
|
schemes: ("http" | "https")[];
|
|
42
46
|
hosts: string[];
|
|
47
|
+
} | {
|
|
48
|
+
id: string;
|
|
49
|
+
/** Local files by extension: 1–16, lower-case letters or digits, no dot. */
|
|
50
|
+
extensions: string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A file resolver's answer as the handler gives it: the producer it prepared
|
|
54
|
+
* (sent to the host), `accepted` once the host selected it (with the stamp of
|
|
55
|
+
* the source it became) and `abandoned` when it never will be: the request
|
|
56
|
+
* was cancelled or answered too late. Exactly one of the two is called.
|
|
57
|
+
*/
|
|
58
|
+
export type ResolvedProducer = {
|
|
59
|
+
kind: "stream";
|
|
60
|
+
producerId: number;
|
|
61
|
+
accepted(reply: {
|
|
62
|
+
stamp: {
|
|
63
|
+
generation: number;
|
|
64
|
+
intentVersion: number;
|
|
65
|
+
};
|
|
66
|
+
}): Promise<void>;
|
|
67
|
+
abandoned(): Promise<void>;
|
|
43
68
|
};
|
|
44
69
|
export declare function sourceResolverClient(transport: Transport): {
|
|
45
|
-
/** Requires source.resolve. One registered host/scheme pair
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
70
|
+
/** Requires source.resolve. One registered host/scheme pair, and one file
|
|
71
|
+
* extension, has one owner (another plugin: `conflict`, reason
|
|
72
|
+
* `resolver-taken`). Resolution shares source.submit arbitration and the
|
|
73
|
+
* original operation ID. This backend supports paired streams with
|
|
74
|
+
* identical header maps; different maps fail explicitly. No header is
|
|
75
|
+
* silently discarded or made global. A failure is sent as its message. */
|
|
49
76
|
register(declaration: SourceResolverDeclaration, handler: (request: {
|
|
50
77
|
url: string;
|
|
51
78
|
signal: AbortSignal;
|
|
52
|
-
}) => Promise<ResolvedMedia>): Promise<{
|
|
79
|
+
}) => Promise<ResolvedMedia | ResolvedProducer>): Promise<{
|
|
53
80
|
dispose(): Promise<void>;
|
|
54
81
|
}>;
|
|
55
82
|
};
|
package/dist/source-resolvers.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
const isProducer = (result) => result?.kind === "stream";
|
|
2
|
+
/** The words of a failure, as the user will read them. */
|
|
3
|
+
const reason = (error) => (error instanceof Error ? error.message : String(error)).slice(0, 2048);
|
|
1
4
|
export function sourceResolverClient(transport) {
|
|
2
5
|
return {
|
|
3
|
-
/** Requires source.resolve. One registered host/scheme pair
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
/** Requires source.resolve. One registered host/scheme pair, and one file
|
|
7
|
+
* extension, has one owner (another plugin: `conflict`, reason
|
|
8
|
+
* `resolver-taken`). Resolution shares source.submit arbitration and the
|
|
9
|
+
* original operation ID. This backend supports paired streams with
|
|
10
|
+
* identical header maps; different maps fail explicitly. No header is
|
|
11
|
+
* silently discarded or made global. A failure is sent as its message. */
|
|
7
12
|
async register(declaration, handler) {
|
|
8
13
|
const active = new Map();
|
|
9
14
|
let disposed = false;
|
|
@@ -13,33 +18,56 @@ export function sourceResolverClient(transport) {
|
|
|
13
18
|
return;
|
|
14
19
|
const controller = new AbortController();
|
|
15
20
|
active.set(request.requestId, controller);
|
|
21
|
+
const live = () => !controller.signal.aborted && !disposed;
|
|
16
22
|
void (async () => {
|
|
23
|
+
let result;
|
|
17
24
|
try {
|
|
18
|
-
|
|
25
|
+
result = await handler({
|
|
19
26
|
url: request.url,
|
|
20
27
|
signal: controller.signal,
|
|
21
28
|
});
|
|
22
|
-
if (!controller.signal.aborted && !disposed) {
|
|
23
|
-
await transport.request("sourceResolvers.respond", {
|
|
24
|
-
requestId: request.requestId,
|
|
25
|
-
result,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
|
-
if (
|
|
31
|
+
if (live())
|
|
31
32
|
await transport
|
|
32
33
|
.request("sourceResolvers.respond", {
|
|
33
34
|
requestId: request.requestId,
|
|
34
|
-
error:
|
|
35
|
+
error: reason(error),
|
|
35
36
|
})
|
|
36
37
|
.catch(() => { });
|
|
38
|
+
active.delete(request.requestId);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
if (!isProducer(result)) {
|
|
43
|
+
if (live())
|
|
44
|
+
await transport.request("sourceResolvers.respond", {
|
|
45
|
+
requestId: request.requestId,
|
|
46
|
+
result,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!live())
|
|
51
|
+
return void (await result.abandoned());
|
|
52
|
+
let reply;
|
|
53
|
+
try {
|
|
54
|
+
// Answered: the host no longer cancels this request.
|
|
55
|
+
active.delete(request.requestId);
|
|
56
|
+
reply = await transport.request("sourceResolvers.respond", {
|
|
57
|
+
requestId: request.requestId,
|
|
58
|
+
result: { kind: "stream", producerId: result.producerId },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
await result.abandoned();
|
|
63
|
+
throw error;
|
|
37
64
|
}
|
|
65
|
+
await result.accepted(reply);
|
|
38
66
|
}
|
|
39
67
|
finally {
|
|
40
68
|
active.delete(request.requestId);
|
|
41
69
|
}
|
|
42
|
-
})();
|
|
70
|
+
})().catch(() => { });
|
|
43
71
|
});
|
|
44
72
|
const offCancel = transport.on("source.resolveCancel", (value) => {
|
|
45
73
|
const request = value;
|
package/dist/wire.d.ts
CHANGED
|
@@ -112,6 +112,12 @@ export interface PackageInfo {
|
|
|
112
112
|
builtin?: boolean;
|
|
113
113
|
source?: WireSource | null;
|
|
114
114
|
dataDirectory?: string | null;
|
|
115
|
+
/** What the plugin was refused because another plugin holds it (player 0.4.x, after 0.4.0). */
|
|
116
|
+
warnings?: {
|
|
117
|
+
code: string;
|
|
118
|
+
slot?: string;
|
|
119
|
+
owner?: string;
|
|
120
|
+
}[];
|
|
115
121
|
}
|
|
116
122
|
/** Where an installed package came from (`packages.install` `source`). */
|
|
117
123
|
export type WireSource = {
|
|
@@ -182,13 +188,24 @@ export interface LayoutBackdrop {
|
|
|
182
188
|
owner: number;
|
|
183
189
|
effective: boolean;
|
|
184
190
|
}
|
|
191
|
+
/** One panel of a position, in tab order (`LayoutSlot.panels`). */
|
|
192
|
+
export interface LayoutPanel {
|
|
193
|
+
owner: number;
|
|
194
|
+
/** The manifest id of the plugin that mounted it. */
|
|
195
|
+
plugin: string;
|
|
196
|
+
/** The tab label: the mount's `title`, else the plugin's name. */
|
|
197
|
+
title: string;
|
|
198
|
+
}
|
|
185
199
|
/** One position (bottom bar or sidebar) as `layout.state` reports it. */
|
|
186
200
|
export interface LayoutSlot {
|
|
201
|
+
/** The current tab's panel (the only panel of the bottom bar). */
|
|
187
202
|
owner: number;
|
|
188
203
|
preferred: boolean;
|
|
189
204
|
effective: boolean;
|
|
190
205
|
reason: "userHidden" | "space" | "fullscreen" | "idle" | null;
|
|
191
206
|
detached: boolean;
|
|
207
|
+
/** The position's panels in tab order; absent from players without sidebar tabs (0.4.0 and older). */
|
|
208
|
+
panels?: LayoutPanel[];
|
|
192
209
|
}
|
|
193
210
|
export interface PlaybackStats {
|
|
194
211
|
backend: string;
|
|
@@ -470,6 +487,8 @@ export declare function createWire(transport: Transport): {
|
|
|
470
487
|
slot: "bottom" | "sidebar";
|
|
471
488
|
visible?: boolean;
|
|
472
489
|
detached?: boolean;
|
|
490
|
+
/** The plugin id of the panel whose tab becomes the current one. */
|
|
491
|
+
active?: string;
|
|
473
492
|
}) => Promise<void>;
|
|
474
493
|
};
|
|
475
494
|
events: {
|
|
@@ -574,7 +593,7 @@ export declare function createWire(transport: Transport): {
|
|
|
574
593
|
register(declaration: import("./source-resolvers.ts").SourceResolverDeclaration, handler: (request: {
|
|
575
594
|
url: string;
|
|
576
595
|
signal: AbortSignal;
|
|
577
|
-
}) => Promise<import("./source-resolvers.ts").ResolvedMedia>): Promise<{
|
|
596
|
+
}) => Promise<import("./source-resolvers.ts").ResolvedMedia | import("./source-resolvers.ts").ResolvedProducer>): Promise<{
|
|
578
597
|
dispose(): Promise<void>;
|
|
579
598
|
}>;
|
|
580
599
|
};
|