@vestra-player/sdk 1.5.6 → 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 CHANGED
@@ -1,5 +1,14 @@
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
+
3
12
  ## 1.5.6
4
13
 
5
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.)
@@ -79,7 +79,8 @@ class StreamHandle {
79
79
  }, 5);
80
80
  }
81
81
  catch (error) {
82
- void this.#end(toError(error));
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
- throw error;
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
- if (error.code !== "aborted")
341
- scope.report(error);
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);
@@ -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
@@ -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);
@@ -3002,9 +3002,15 @@ export interface Stream extends Disposable {
3002
3002
  /**
3003
3003
  * The stream stopped for a reason outside this plugin: `error` is null when
3004
3004
  * another source replaced it or the user closed it, otherwise the failure
3005
- * (`stream`, `invalid` for a bad frame, …). Not called when the plugin
3006
- * itself stops it (`stop()`, `dispose()`, deactivation): the promise of
3007
- * `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.
3008
3014
  */
3009
3015
  onStop(listener: (error: PluginError | null) => void): Disposable;
3010
3016
  /**
@@ -3147,7 +3153,8 @@ export interface Player {
3147
3153
  * activation, pausing and acknowledgement. Frames travel over a local
3148
3154
  * connection the SDK sets up: a Unix socket on macOS and Linux, loopback
3149
3155
  * TCP with a token on Windows. Rejects with a {@link StreamStartError}
3150
- * (`timeout`), `stream` or `device`.
3156
+ * (`timeout`), `stream`, `device`, or `replaced` when another source
3157
+ * replaced it (or it was closed) before it ran.
3151
3158
  *
3152
3159
  * @example
3153
3160
  * ```ts
@@ -3937,6 +3944,14 @@ export interface FileResolverOptions {
3937
3944
  /**
3938
3945
  * What a file resolver returns: a stream, with the same frame contract as
3939
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.
3940
3955
  */
3941
3956
  export interface ResolvedStream {
3942
3957
  /** Always `"stream"`. */
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.6: full reference
1
+ # @vestra-player/sdk 1.5.7: full reference
2
2
 
3
3
  > Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
4
4
 
@@ -2616,7 +2616,17 @@ interface Stream extends Disposable {
2616
2616
  /** The stream is producing frames. */
2617
2617
  readonly running: boolean;
2618
2618
  /**
2619
- * The stream stopped for a reason outside this plugin: `error` is null when another source replaced it or the user closed it, otherwise the failure (`stream`, `invalid` for a bad frame, …). Not called when the plugin itself stops it (`stop()`, `dispose()`, deactivation): the promise of `stop()` is that notification.
2619
+ * The stream stopped for a reason outside this plugin: `error` is null when
2620
+ * another source replaced it or the user closed it, otherwise the failure
2621
+ * (`stream` with the player's text, `invalid` for a bad frame, …). Not
2622
+ * called when the plugin itself stops it (`stop()`, `dispose()`,
2623
+ * deactivation): the promise of `stop()` is that notification.
2624
+ *
2625
+ * The player retires a stream's producer as soon as it is replaced or
2626
+ * closed; the SDK reads the player's state to tell that from a failure
2627
+ * (before 1.5.7 a replaced or closed stream could get `unavailable`
2628
+ * "producer absent"). Without `playback.read` a failure the SDK has not
2629
+ * seen yet is reported as null.
2620
2630
  */
2621
2631
  onStop(listener: (error: PluginError | null) => void): Disposable;
2622
2632
  /**
@@ -2758,7 +2768,8 @@ interface Player {
2758
2768
  * activation, pausing and acknowledgement. Frames travel over a local
2759
2769
  * connection the SDK sets up: a Unix socket on macOS and Linux, loopback
2760
2770
  * TCP with a token on Windows. Rejects with a StreamStartError
2761
- * (`timeout`), `stream` or `device`.
2771
+ * (`timeout`), `stream`, `device`, or `replaced` when another source
2772
+ * replaced it (or it was closed) before it ran.
2762
2773
  */
2763
2774
  openStream(options: StreamOptions): Promise<Stream>;
2764
2775
  /** Pause or resume. */
@@ -3389,6 +3400,14 @@ interface FileResolverOptions {
3389
3400
  What a file resolver returns: a stream, with the same frame contract as
3390
3401
  StreamOptions (size limits, `frame()` on the audio clock).
3391
3402
 
3403
+ After `resolve` returns, the SDK starts the stream while the open waits.
3404
+ If that fails, the open fails with the player's words instead of the
3405
+ SDK's (the detail goes to the log): `device` "音频输出设备不可用",
3406
+ `timeout` "启动超时", `busy` "播放器正忙,请稍后重试", other start-up
3407
+ failures "无法启动播放". An error thrown by `frame()` keeps its message.
3408
+ To give your own text for a file you can tell is unusable (a bad header),
3409
+ check it in `resolve` and throw there.
3410
+
3392
3411
  ```ts
3393
3412
  interface ResolvedStream {
3394
3413
  /** Always `"stream"`. */
@@ -4260,6 +4279,10 @@ interface FakeProducer {
4260
4279
  videoFrames: number;
4261
4280
  /** `true` once the plugin released the producer (stream stopped or disposed). */
4262
4281
  released: boolean;
4282
+ /**
4283
+ * `true` once the player retired the producer, as the real player does at once: another source replaced it (`open`, another stream, a resolved file), the source was closed, or TestHost.failStream failed it. A retired producer's `producer.state` answers `unavailable`.
4284
+ */
4285
+ retired: boolean;
4263
4286
  /**
4264
4287
  * `true` if the frame connection closed while this stream was still the player's source: the player reports that as a playback failure ("producer transport closed"). `stop()` closes the source first.
4265
4288
  */
@@ -4854,6 +4877,12 @@ class TestHost implements Transport {
4854
4877
  * @param phase - The phase to end in (default `"ready"`).
4855
4878
  */
4856
4879
  openElsewhere(source: string, phase?: SourceState["phase"]): void;
4880
+ /**
4881
+ * Simulate a generated stream failing in the player (its audio device lost, its frame connection broken), like the player: the source fails with `message` (`phase: "error"`) and the producer is retired at once. The stream's `onStop` listeners get a `stream` error with `message`.
4882
+ * @param producerId - The producer, from TestHost.producers.
4883
+ * @param message - The player's failure text.
4884
+ */
4885
+ failStream(producerId: number, message: string): void;
4857
4886
  /**
4858
4887
  * Press a binding like the host's table would: panel-focus bindings first when `panel` is true, then global ones; the earliest registration wins a conflict. Resolves after the command ran; rejects when nothing is bound or the command is disabled or busy. Bindings use the reference syntax (`"Space"`, `"Ctrl+O"`, `"Mouse:WheelUp"`); the player's core actions match first, and user overrides in `bindings.overlay` apply.
4859
4888
  */
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.6: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
3
+ > @vestra-player/sdk 1.5.7: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
4
4
 
5
5
  **How plugins run**
6
6
 
@@ -150,8 +150,8 @@ test("shows the position", async () => {
150
150
  - [Concept: Windows, panels, settings pages, dialogs and the OSD](https://vestrasys.github.io/vestra-player-sdk/documents/Windows__panels__settings_pages__dialogs_and_the_OSD.html): L281–626
151
151
  - [How-to: Keep a slider responsive while dragging](https://vestrasys.github.io/vestra-player-sdk/documents/Keep_a_slider_responsive_while_dragging.html): L627–801
152
152
  - [How-to: Remember a window's state between runs](https://vestrasys.github.io/vestra-player-sdk/documents/Remember_a_window_s_state_between_runs.html): L802–931
153
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4153
154
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4154–5084
153
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4172
154
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4173–5113
155
155
 
156
156
  ## API
157
157
 
@@ -162,24 +162,24 @@ Where each module of `plugin` is declared:
162
162
  - plugin.windows: Windows L1554
163
163
  - plugin.panels: Panels L2005
164
164
  - plugin.dialogs: Dialogs L2287
165
- - plugin.player: Player L2716
166
- - plugin.video: VideoFilter L2964, VideoView L3027, RenderOptions L3069
167
- - plugin.audio: AudioFilter L2988
168
- - plugin.subtitles: Subtitles L3167
169
- - plugin.media: MediaCapture L3325
170
- - plugin.resolvers: Resolvers L3422
171
- - plugin.packages: Packages L3643
172
- - plugin.osd: Osd L2901
173
- - plugin.host: Host L2864
174
- - plugin.clipboard: Clipboard L3750
175
- - plugin.power: Power L3770
176
- - plugin.tools: Tools L3803
177
- - plugin.storage: Storage L3846
178
- - plugin.system: System L3869
179
- - plugin.secrets: Secrets L3887
165
+ - plugin.player: Player L2726
166
+ - plugin.video: VideoFilter L2975, VideoView L3038, RenderOptions L3080
167
+ - plugin.audio: AudioFilter L2999
168
+ - plugin.subtitles: Subtitles L3178
169
+ - plugin.media: MediaCapture L3336
170
+ - plugin.resolvers: Resolvers L3441
171
+ - plugin.packages: Packages L3662
172
+ - plugin.osd: Osd L2912
173
+ - plugin.host: Host L2875
174
+ - plugin.clipboard: Clipboard L3769
175
+ - plugin.power: Power L3789
176
+ - plugin.tools: Tools L3822
177
+ - plugin.storage: Storage L3865
178
+ - plugin.system: System L3888
179
+ - plugin.secrets: Secrets L3906
180
180
  - plugin.window: MainWindow L2128
181
181
  - plugin.app: App L2209
182
- - plugin.bindings: Bindings L3720
182
+ - plugin.bindings: Bindings L3739
183
183
  - plugin.ui: Ui L1441
184
184
  - plugin.timeline: Timeline L2085
185
185
  - plugin.drops: Drops L1851
@@ -190,14 +190,14 @@ Where each module of `plugin` is declared:
190
190
 
191
191
  Complete plugins with tests, in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples) and in llms-full.txt:
192
192
 
193
- - hello: The README Quick start: one menu command and an on-screen message. L5498–5587
194
- - blur: A tool window with one slider that blurs the playing video. L5588–5849
195
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5850–6182
196
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6183–6367
197
- - panel-menu: A sidebar panel with its own right-click menu. L6368–6573
198
- - menus: Radio menu items, separator groups and a menu item whose title changes. L6574–6733
199
- - theme-widgets: A tool window drawn with the player's theme and widgets. L6734–6897
200
- - markdown: A window that shows a Markdown document. L6898–7091
193
+ - hello: The README Quick start: one menu command and an on-screen message. L5527–5616
194
+ - blur: A tool window with one slider that blurs the playing video. L5617–5878
195
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5879–6211
196
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6212–6396
197
+ - panel-menu: A sidebar panel with its own right-click menu. L6397–6602
198
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L6603–6762
199
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L6763–6926
200
+ - markdown: A window that shows a Markdown document. L6927–7120
201
201
 
202
202
  ## Optional
203
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.6",
3
+ "version": "1.5.7",
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": {