@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.
@@ -8,7 +8,7 @@ export type ResolvedResource = {
8
8
  headers?: Record<string, string>;
9
9
  };
10
10
  /**
11
- * What a resolver returns ({@link ResolverOptions.resolve}): the playable
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 has one owner.
46
- * Resolution shares source.submit arbitration and the original operation ID.
47
- * This backend supports paired streams with identical header maps; different
48
- * maps fail explicitly. No header is silently discarded or made global. */
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
  };
@@ -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 has one owner.
4
- * Resolution shares source.submit arbitration and the original operation ID.
5
- * This backend supports paired streams with identical header maps; different
6
- * maps fail explicitly. No header is silently discarded or made global. */
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
- const result = await handler({
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 (!controller.signal.aborted && !disposed) {
31
+ if (live())
31
32
  await transport
32
33
  .request("sourceResolvers.respond", {
33
34
  requestId: request.requestId,
34
- error: String(error).slice(0, 2048),
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {