@vestra-player/sdk 1.6.8 → 1.6.10

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.
@@ -93,7 +93,7 @@ export interface Disposable {
93
93
  * | `busy` | The player is still doing a previous call of the same kind (a dialog is open, a step is in flight). |
94
94
  * | `conflict` | Another plugin owns the resource (the sequence provider, the display layer, the backdrop, a menu path), or this plugin already uses the id (a window or command id). |
95
95
  * | `unavailable` | Not possible right now (no ready media, no user interface, fullscreen, a disabled core action). |
96
- * | `unsupported` | Not possible on this system, build or source (a platform gap, software rendering, a source that cannot seek, a filter on a generated stream). |
96
+ * | `unsupported` | Not possible on this system, build or source (a platform gap, software rendering, a source that cannot seek, a filter on a generated stream, an export the player cannot make as asked: a stream copy, a source bitrate it cannot tell). |
97
97
  * | `backend` | Loading or resolving failed; `message` is the backend's reason. |
98
98
  * | `failed` | The operation ran and failed (`media.ready()` load error, a non-zero `tools.run` exit, the system refused). |
99
99
  * | `replaced` | Accepted, but another source replaced it before it finished. |
@@ -114,6 +114,7 @@ export interface Disposable {
114
114
  * | `unknown`, `rejected`, `expired` | After a transport problem the SDK could not confirm the outcome (does not happen in normal operation). |
115
115
  * | `activation` | The plugin opened a tool window while its activation was still pending and the player could not track it. |
116
116
  * | `command` | The player could not run or update a command it no longer knows for this plugin (removed meanwhile, or another plugin's). |
117
+ * | `export-failed` | An export job ({@link ExportJob}) failed: the code of its `outcome.error`, not a rejection; `message` has the worker's or FFmpeg's reason. |
117
118
  * | `input-overflow` | `openStream` input: the player's queue of physical key transitions was full; the input was revoked and its keys released. |
118
119
  * | `internal` | An internal step of the player failed (keeping an operation's result, a subtitle or media job); `message` has the reason. |
119
120
  * | `javascript`, `slint` | {@link Packages.validate}: the package's entry module does not pass `node --check` / one of its Slint files does not compile; `message` has the diagnostics. |
@@ -4216,6 +4217,36 @@ export interface TimelineMark {
4216
4217
  /** The manifest id of the plugin that published it. */
4217
4218
  readonly plugin: string;
4218
4219
  }
4220
+ /**
4221
+ * How a timeline range is drawn; readers choose colours by it.
4222
+ *
4223
+ * - `"selection"`: a span the user picked to act on, such as a clip to
4224
+ * export. The player's seek bar draws it in the accent colour.
4225
+ * - `"region"`: a part of the media the plugin describes, such as an intro
4226
+ * or a chapter's span. Drawn dimmer, under selections.
4227
+ *
4228
+ * @since 1.6.9
4229
+ * @category Playback
4230
+ */
4231
+ export type RangeStyle = "selection" | "region";
4232
+ /**
4233
+ * One range on the seek bar (player 0.7.6).
4234
+ *
4235
+ * @since 1.6.9
4236
+ * @category Playback
4237
+ */
4238
+ export interface TimelineRange {
4239
+ /** Seconds from the start. */
4240
+ readonly start: number;
4241
+ /** Seconds from the start, after `start`. */
4242
+ readonly end: number;
4243
+ /** How it is drawn. */
4244
+ readonly style: RangeStyle;
4245
+ /** Label, `null` without one. */
4246
+ readonly title: string | null;
4247
+ /** The manifest id of the plugin that published it. */
4248
+ readonly plugin: string;
4249
+ }
4219
4250
  /**
4220
4251
  * This plugin's own marks; disposing it removes them.
4221
4252
  *
@@ -4273,6 +4304,54 @@ export interface TimelineLayer extends Disposable {
4273
4304
  }[], options?: {
4274
4305
  ifCurrent?: PlayerState;
4275
4306
  }): void;
4307
+ /**
4308
+ * Replace the layer's ranges (last write wins; `[]` clears them), spans of
4309
+ * the media drawn on the seek bar (player 0.7.6). The marks and the ranges
4310
+ * of a layer are replaced on their own: `set` keeps the ranges and
4311
+ * `setRanges` keeps the marks. Disposing the layer, its scope or stopping
4312
+ * the plugin removes both, and a new source clears every layer.
4313
+ *
4314
+ * At most 100 ranges (`TIMELINE_LIMITS.ranges`), `0 ≤ start < end`, `end`
4315
+ * within the media while its duration is known, titles ≤ 100 characters.
4316
+ * Ranges may overlap. Failures go to `onError`; with `ifCurrent`, a
4317
+ * write for another source is dropped silently. A title is data or a
4318
+ * {@link Text} the player translates, as in `set`.
4319
+ *
4320
+ * Players before 0.7.6 build `plugin` from an SDK without `setRanges`, so
4321
+ * the call throws a `TypeError` there: declare
4322
+ * `"minPlayerVersion": "0.7.6"`.
4323
+ *
4324
+ * @example Show the clip range on the seek bar while a panel is open
4325
+ * The span the user picked shows on the seek bar in the accent colour
4326
+ * while the plugin's tab is showing, and goes when it is not. Two point
4327
+ * marks would show the ends but not the span between them.
4328
+ * ```ts
4329
+ * declare const panel: Panel;
4330
+ * declare const clip: { start: number; end: number };
4331
+ * // ---cut---
4332
+ * const layer = plugin.timeline.layer();
4333
+ * // Only while the tab is on screen; [] takes the range away.
4334
+ * const show = () =>
4335
+ * layer.setRanges(
4336
+ * panel.state?.effective
4337
+ * ? [{ start: clip.start, end: clip.end, style: "selection", title: "Clip" }]
4338
+ * : [],
4339
+ * { ifCurrent: plugin.player.state },
4340
+ * );
4341
+ * panel.onChange(show);
4342
+ * ```
4343
+ *
4344
+ * @capability timeline.marks
4345
+ * @since 1.6.9
4346
+ */
4347
+ setRanges(ranges: {
4348
+ start: number;
4349
+ end: number;
4350
+ style: RangeStyle;
4351
+ title?: string | Text;
4352
+ }[], options?: {
4353
+ ifCurrent?: PlayerState;
4354
+ }): void;
4276
4355
  }
4277
4356
  /**
4278
4357
  * `plugin.timeline`: marks on the seek bar. Every plugin publishes one
@@ -4303,6 +4382,38 @@ export interface TimelineLayer extends Disposable {
4303
4382
  export interface Timeline {
4304
4383
  /** Every layer merged, by time (`playback.read`). Empty on a new source. */
4305
4384
  readonly marks: readonly TimelineMark[];
4385
+ /**
4386
+ * Every layer's ranges merged, by start (`playback.read`; player 0.7.6:
4387
+ * on older players `ranges` does not exist and reads `undefined`). Empty
4388
+ * on a new source. Kept current like `marks` (with `events`): read it in
4389
+ * a {@link View.bind} function and the view follows it. `onChange` runs
4390
+ * only when the marks change.
4391
+ *
4392
+ * @example Draw the published ranges in a seek bar of your own
4393
+ * A bottom bar draws a band for every range on the seek bar, whoever
4394
+ * published it, as fractions of the duration.
4395
+ * ```ts
4396
+ * declare const bar: View;
4397
+ * // ---cut---
4398
+ * bar.bind(() => {
4399
+ * // Reading `ranges` here runs the binding again when they change.
4400
+ * const duration = plugin.player.state.duration ?? 0;
4401
+ * return {
4402
+ * ranges: duration
4403
+ * ? plugin.timeline.ranges.map((range) => ({
4404
+ * from: range.start / duration,
4405
+ * to: range.end / duration,
4406
+ * selection: range.style === "selection",
4407
+ * }))
4408
+ * : [],
4409
+ * };
4410
+ * });
4411
+ * ```
4412
+ *
4413
+ * @capability playback.read
4414
+ * @since 1.6.9
4415
+ */
4416
+ readonly ranges: readonly TimelineRange[];
4306
4417
  /**
4307
4418
  * The merged list changed (needs `events`; at most every 100 ms).
4308
4419
  *
@@ -6700,12 +6811,19 @@ export interface Osd {
6700
6811
  *
6701
6812
  * @param text - The template, 1–200 characters once translated and
6702
6813
  * formatted; line breaks become spaces.
6814
+ * With `options.actions` (player 0.7.6) the message ends in up to two
6815
+ * buttons, each running one of the plugin's own commands
6816
+ * ({@link OsdShowOptions}). Older players show the message without them.
6817
+ *
6703
6818
  * @param args - Values for its placeholders (player 0.4.3; older players
6704
- * show the template as it is).
6819
+ * show the template as it is). Pass `null` with a `Text` and options.
6820
+ * @param options - Action buttons after the text (SDK 1.6.9).
6705
6821
  * @returns Resolves once the message is on screen.
6706
6822
  *
6707
6823
  * @throws `invalid` (rejects) The shown text is empty, only white space,
6708
- * or longer than 200 characters, or `args` is not a {@link TemplateArgs}.
6824
+ * or longer than 200 characters, or `args` is not a {@link TemplateArgs};
6825
+ * or there are more than 2 actions, a title is empty or longer than 40
6826
+ * characters once translated, or a command is not one of the plugin's.
6709
6827
  * @throws `unavailable` (rejects) The player runs without a user interface.
6710
6828
  * @throws `capability` (rejects) The manifest does not declare `ui.osd`.
6711
6829
  *
@@ -6719,9 +6837,28 @@ export interface Osd {
6719
6837
  * await plugin.osd.show("Speed {}×", [plugin.player.state.rate]);
6720
6838
  * ```
6721
6839
  *
6840
+ * @example Offer "Show in Folder" after saving a file
6841
+ * The OSD reads "Saved clip.mp4 [Show in Folder]" for about 4 seconds,
6842
+ * longer while the pointer is on it. A click runs the plugin's own
6843
+ * `reveal` command, which knows the last saved file; the message goes.
6844
+ * ```ts
6845
+ * declare const path: string;
6846
+ * // ---cut---
6847
+ * let saved = path; // the command acts on the last saved file
6848
+ * plugin.commands.add({
6849
+ * id: "reveal",
6850
+ * title: "Show Last Saved File in Folder",
6851
+ * enabled: () => saved !== "",
6852
+ * run: () => plugin.system.reveal(saved),
6853
+ * });
6854
+ * await plugin.osd.show("Saved {}", [saved.split(/[\\/]/).pop() ?? saved], {
6855
+ * actions: [{ title: "Show in Folder", command: "reveal" }], // one button
6856
+ * });
6857
+ * ```
6858
+ *
6722
6859
  * @since 1.0.0
6723
6860
  */
6724
- show(text: TextValue, args?: TemplateArgs): Promise<void>;
6861
+ show(text: TextValue, args?: TemplateArgs | null, options?: OsdShowOptions): Promise<void>;
6725
6862
  /**
6726
6863
  * Create this plugin's information block.
6727
6864
  *
@@ -6748,6 +6885,54 @@ export interface Osd {
6748
6885
  */
6749
6886
  panel(): OsdPanel;
6750
6887
  }
6888
+ /**
6889
+ * One button at the end of an OSD message ({@link Osd.show}, player 0.7.6).
6890
+ *
6891
+ * @since 1.6.9
6892
+ * @category UI
6893
+ */
6894
+ export interface OsdAction {
6895
+ /**
6896
+ * The button's label: English source text the player translates with the
6897
+ * plugin's catalog, or a {@link Text}; 1–40 characters once translated
6898
+ * (`OSD_LIMITS.actionChars`). A verb, as on a menu item: "Show in
6899
+ * Folder", "Copy", "Undo".
6900
+ */
6901
+ title: TextValue;
6902
+ /**
6903
+ * The local id of one of this plugin's own commands
6904
+ * ({@link CommandOptions.id}), declared when the message is shown. A
6905
+ * click runs it as if the user picked it from a menu: a disabled command
6906
+ * does nothing. The command takes no arguments, so it acts on what the
6907
+ * plugin remembers (the file it just saved).
6908
+ */
6909
+ command: string;
6910
+ }
6911
+ /**
6912
+ * Options of {@link Osd.show} (SDK 1.6.9).
6913
+ *
6914
+ * @remarks
6915
+ * A message with actions stays about 4 seconds instead of 2, and does not
6916
+ * fade while the pointer is on it and moving: it goes 2 seconds after the
6917
+ * pointer leaves or rests (the player's idle time), and at the latest 30
6918
+ * seconds after it showed, hovered or not. A click on a button runs its
6919
+ * command and removes the message.
6920
+ * It is still one line: the buttons follow the text. Buttons take no
6921
+ * keyboard focus; screen readers can press them. A newer message, from any
6922
+ * plugin or the player, replaces the message with its buttons, and the
6923
+ * buttons go when the plugin stops. While its buttons show, the plugin is
6924
+ * not stopped for being idle.
6925
+ *
6926
+ * Players before 0.7.6 ignore `actions` and show the text alone for 2
6927
+ * seconds, so do not make an action the only way to reach something.
6928
+ *
6929
+ * @since 1.6.9
6930
+ * @category UI
6931
+ */
6932
+ export interface OsdShowOptions {
6933
+ /** At most 2 buttons (`OSD_LIMITS.actions`), in order after the text. */
6934
+ actions?: readonly OsdAction[];
6935
+ }
6751
6936
  /**
6752
6937
  * The state of a filter, display layer or render options handle.
6753
6938
  * After `failed` nothing retries automatically until the next `set()`,
@@ -8110,11 +8295,30 @@ export interface Snapshot extends Disposable {
8110
8295
  * Start exporting; the job runs in the host's offline worker.
8111
8296
  * One snapshot can be exported several times.
8112
8297
  *
8298
+ * A **stream copy** (`format: "copy"`, player 0.7.6) writes the video
8299
+ * stream and the snapshot's audio track as they are in the file, without
8300
+ * decoding: lossless and about as fast as the disk, but a copy can only
8301
+ * begin on a keyframe. It starts at the video's last keyframe at or before
8302
+ * the snapshot time (its first keyframe when the video itself starts
8303
+ * later; keyframes an MP4 edit list discards do not count) and is cut at
8304
+ * `end`, where it may run a few frames past `end`. An open GOP can write
8305
+ * a frame or two that present before the keyframe; `start` includes them.
8306
+ * The finished job's `outcome` gives the `start` and `end` actually
8307
+ * written; a plugin that previews the cut (with `plugin.tools.run`
8308
+ * and ffprobe's packet flags, say) shows those once the job is done.
8309
+ * Copies need a raw snapshot (effects and burned-in subtitles need a
8310
+ * re-encode) and leave subtitle tracks and chapters out. `auto` writes
8311
+ * MP4 for H.264, HEVC, AV1 or MPEG-4 video with AAC, MP3, AC-3, E-AC-3 or
8312
+ * ALAC audio (or none), and MKV for anything else. A copy the player
8313
+ * cannot make (no video stream, a cover image, an external audio track,
8314
+ * codecs `mp4` cannot hold) ends `failed` with the code `unsupported`.
8315
+ *
8113
8316
  * @capability media.read and media.control
8114
8317
  * @throws `released` (rejects) The snapshot was already released.
8115
8318
  * @throws `invalid` (rejects) Bad options (`quality` outside 1–100, a
8116
8319
  * relative destination, a clip `end` not after the snapshot time or
8117
- * past the duration).
8320
+ * past the duration, an {@link ExportEncoding} value outside its set,
8321
+ * `copy` from a processed snapshot).
8118
8322
  * @throws `busy` (rejects) This plugin runs 2 exports (or the player 8).
8119
8323
  * @throws `unavailable` (rejects) No FFmpeg, or the export worker could not
8120
8324
  * start.
@@ -8134,18 +8338,48 @@ export interface Snapshot extends Disposable {
8134
8338
  * if (end.status !== "completed") await plugin.osd.show("Export {}", [end.status]);
8135
8339
  * ```
8136
8340
  *
8341
+ * @example Cut without re-encoding and report where it starts
8342
+ * The clip is copied as it is in the file. A copy starts on a keyframe,
8343
+ * so the OSD says where it actually starts; a file the player cannot
8344
+ * copy ends with `unsupported`, and the plugin offers a re-encode instead.
8345
+ * ```ts
8346
+ * declare const snapshot: Snapshot;
8347
+ * // ---cut---
8348
+ * const job = await snapshot.export({ format: "copy", destination: "/tmp/clip.mp4", end: snapshot.time + 20 });
8349
+ * const state = await job.done;
8350
+ * const outcome = state.outcome;
8351
+ * // The copy starts on a keyframe: outcome.start says where.
8352
+ * if (outcome && "destination" in outcome)
8353
+ * await plugin.osd.show("Saved {} from {} s", [outcome.destination, (outcome.start ?? snapshot.time).toFixed(1)]);
8354
+ * // Codecs the container cannot hold, a cover image, …
8355
+ * else if (outcome && "error" in outcome && outcome.error.code === "unsupported")
8356
+ * await plugin.osd.show("Fast cut is not possible for this file");
8357
+ * ```
8358
+ *
8137
8359
  * @since 1.0.0
8138
8360
  */
8139
8361
  export(options: ExportOptions): Promise<ExportJob>;
8140
8362
  }
8141
8363
  /**
8142
8364
  * One frame as PNG, JPEG (`quality` 1–100, default 90) or BMP (24-bit), or
8143
- * a clip as MP4.
8365
+ * a clip: re-encoded as MP4 (`mp4`, frame-accurate, optional
8366
+ * {@link ExportEncoding}) or copied without re-encoding (`copy`, player
8367
+ * 0.7.6, starting on a keyframe).
8144
8368
  *
8145
8369
  * `destination` is never overwritten if it exists. The extension does not
8146
- * choose the format. `sampleTime` picks the frame (default: the snapshot
8147
- * time); `end` is the clip's end in source seconds. On Windows the export
8148
- * worker needs FFmpeg 6.1 or newer.
8370
+ * choose the format (a copy's extension is set to its container).
8371
+ * `sampleTime` picks the frame (default: the snapshot time); `end` is the
8372
+ * clip's end in source seconds. On Windows the export worker needs FFmpeg
8373
+ * 6.1 or newer.
8374
+ *
8375
+ * **Which players have `copy`, `encoding` and the progress and range of
8376
+ * a job**: player 0.7.6 and later. Declare `"minPlayerVersion": "0.7.6"`,
8377
+ * or, to keep older players and offer these only where they exist, compare
8378
+ * {@link AppInfo.version | plugin.app.version} with `0.7.6`, as for any
8379
+ * newer API. Do not probe by trying: on an older player the `plugin`
8380
+ * object comes from that player's own, older SDK, which rejects `copy` with
8381
+ * `invalid` but builds a re-encode request without the `encoding` it does
8382
+ * not know, so the clip is encoded with the fixed settings and no error.
8149
8383
  *
8150
8384
  * @since 1.0.0
8151
8385
  * @category Media
@@ -8167,19 +8401,132 @@ export type ExportOptions = {
8167
8401
  /** Integer 1–100, default 90. */
8168
8402
  quality?: number;
8169
8403
  } | {
8170
- /** A clip from the snapshot time. */
8404
+ /** A clip from the snapshot time, re-encoded as H.264 + AAC. */
8171
8405
  format: "mp4";
8172
8406
  /** Absolute path of the new file. */
8173
8407
  destination: string;
8174
8408
  /** Clip end, source seconds. */
8175
8409
  end: number;
8410
+ /**
8411
+ * Encoder settings (player 0.7.6); without it, the settings before
8412
+ * they existed.
8413
+ *
8414
+ * @since 1.6.10
8415
+ */
8416
+ encoding?: ExportEncoding;
8417
+ } | {
8418
+ /**
8419
+ * A clip copied stream by stream, without re-encoding (player 0.7.6):
8420
+ * lossless and about as fast as the disk. It starts on the video
8421
+ * keyframe at or before the snapshot time; see {@link Snapshot.export}.
8422
+ *
8423
+ * @since 1.6.10
8424
+ */
8425
+ format: "copy";
8426
+ /**
8427
+ * Absolute path of the new file. The player gives it the extension of
8428
+ * the container it writes (`.mp4` or `.mkv`); the finished job's
8429
+ * `outcome.destination` is the actual path.
8430
+ */
8431
+ destination: string;
8432
+ /** Clip end, source seconds. */
8433
+ end: number;
8434
+ /**
8435
+ * `auto` (default): MP4 when the codecs fit it, otherwise MKV. `mp4`
8436
+ * fails with `unsupported` when they do not; `mkv` always writes MKV.
8437
+ */
8438
+ container?: "auto" | "mp4" | "mkv";
8176
8439
  };
8440
+ /**
8441
+ * The encoder settings of a re-encoded clip (`format: "mp4"`, player
8442
+ * 0.7.6): a closed set of values, never encoder arguments. Every field is
8443
+ * optional; the defaults are the settings clips had before: CRF 18, the
8444
+ * fastest speed, the source's size and frame rate, AAC 192 kb/s, one thread.
8445
+ * A value outside its set rejects the export with `invalid`. `videoBitrate:
8446
+ * "source"` replaces the CRF with the source's bitrate, for a "Same as
8447
+ * source" preset.
8448
+ *
8449
+ * @example A small file
8450
+ * A 720p, 30 fps clip at a lower quality, encoded with the cores the player
8451
+ * can spare.
8452
+ * ```ts
8453
+ * declare const snapshot: Snapshot;
8454
+ * // ---cut---
8455
+ * // Each field is a ceiling or a choice from a closed set.
8456
+ * const job = await snapshot.export({
8457
+ * format: "mp4",
8458
+ * destination: "/tmp/small.mp4",
8459
+ * end: snapshot.time + 30,
8460
+ * encoding: { crf: 26, maxHeight: 720, fps: 30, audioBitrate: 128, threads: "auto" },
8461
+ * });
8462
+ * ```
8463
+ *
8464
+ * @since 1.6.10
8465
+ * @category Media
8466
+ */
8467
+ export interface ExportEncoding {
8468
+ /**
8469
+ * H.264 quality, an integer 14–30 (x264's CRF): lower is a better picture
8470
+ * and a bigger file. Default 18.
8471
+ */
8472
+ crf?: number;
8473
+ /**
8474
+ * Encoding effort: `faster` (default), `balanced` or `smaller` (slower,
8475
+ * a smaller file at the same quality); x264's `veryfast`, `medium` and
8476
+ * `slow`.
8477
+ */
8478
+ speed?: "faster" | "balanced" | "smaller";
8479
+ /**
8480
+ * The tallest picture: a taller one is scaled down to this height,
8481
+ * keeping its shape; a shorter one is left as it is. Default: the
8482
+ * source's size.
8483
+ */
8484
+ maxHeight?: 1080 | 720 | 480;
8485
+ /**
8486
+ * The highest frame rate: a faster source drops frames to it; a slower
8487
+ * one is left as it is. Default: the source's rate.
8488
+ */
8489
+ fps?: 60 | 30;
8490
+ /** AAC audio bitrate in kb/s. Default 192. */
8491
+ audioBitrate?: 128 | 192 | 256;
8492
+ /**
8493
+ * `single` (default) encodes on one thread, leaving the other cores to
8494
+ * playback. `auto` lets the player use more: half the logical
8495
+ * processors, at most 8, so playback and the interface keep their rate.
8496
+ * From player 0.7.6 every export runs at below-normal priority (as
8497
+ * HandBrake and OBS run their encoders), with either setting.
8498
+ */
8499
+ threads?: "single" | "auto";
8500
+ /**
8501
+ * `source`: aim at the source video's average bitrate instead of a
8502
+ * quality level (peaks up to 1.5 times it), so the file comes out about
8503
+ * the size of that stretch of the source: a "Same as source" preset. Not
8504
+ * together with `crf` (`invalid`). A source whose bitrate the player
8505
+ * cannot tell ends the job `failed` with `unsupported`.
8506
+ */
8507
+ videoBitrate?: "source";
8508
+ }
8177
8509
  /**
8178
8510
  * An export job's state. `status` is `queued`, `running`, `cancelling`,
8179
8511
  * `completed`, `failed` or `cancelled`; once terminal, `outcome` is
8180
8512
  * `{ destination }`, `{ error: { code, message } }` or `{ cancelled: true }`.
8181
8513
  * `facts` describes what the worker produced (versions, range, streams).
8182
8514
  *
8515
+ * From player 0.7.6:
8516
+ * - a finished clip's `outcome` also has `start` and `end`, the source
8517
+ * times of its first frame and of the end of its last frame as written: a
8518
+ * copy's earliest written frame (its keyframe, or the leading frames of
8519
+ * an open GOP) and actual end; a re-encode's start and start
8520
+ * plus the encoded length.
8521
+ * - `progress` is `{ done, total }` in seconds of the clip while FFmpeg
8522
+ * writes it (at most 4 updates a second; the SDK reads it about every
8523
+ * 350 ms), `null` while unknown: before writing starts, and always for a
8524
+ * frame. Show an indeterminate bar then. After `done` reaches `total` the
8525
+ * player still checks the file before the job completes.
8526
+ * - `error.code` is `export-failed`, or `unsupported` for an export the
8527
+ * player cannot make as asked (a stream copy it cannot make, a
8528
+ * `videoBitrate: "source"` whose source bitrate it cannot tell).
8529
+ *
8183
8530
  * @since 1.0.0
8184
8531
  * @category Media
8185
8532
  */
package/dist/wire.d.ts CHANGED
@@ -262,6 +262,19 @@ export interface WireMark {
262
262
  title: string | null;
263
263
  plugin: string;
264
264
  }
265
+ /** One timeline range as the player merges them (player 0.7.6). */
266
+ export interface WireRange {
267
+ start: number;
268
+ end: number;
269
+ style: "selection" | "region";
270
+ title: string | null;
271
+ plugin: string;
272
+ }
273
+ /** `timeline.marks` and `timeline.changed`; `ranges` from player 0.7.6. */
274
+ export interface WireTimeline {
275
+ marks: WireMark[];
276
+ ranges?: WireRange[];
277
+ }
265
278
  /** The backdrop as `layout.state` reports it; `null` without one. */
266
279
  export interface LayoutBackdrop {
267
280
  owner: number;
@@ -568,9 +581,7 @@ export declare function createWire(transport: Transport): {
568
581
  };
569
582
  /** Mark layers on the seek bar (`plugin.timeline`). */
570
583
  timeline: {
571
- marks: () => Promise<{
572
- marks: WireMark[];
573
- }>;
584
+ marks: () => Promise<WireTimeline>;
574
585
  set: (marks: {
575
586
  time: number;
576
587
  kind: string;
@@ -578,6 +589,15 @@ export declare function createWire(transport: Transport): {
578
589
  }[], sourceId?: number) => Promise<{
579
590
  applied: boolean;
580
591
  }>;
592
+ /** Player 0.7.6. */
593
+ setRanges: (ranges: {
594
+ start: number;
595
+ end: number;
596
+ style: string;
597
+ title?: string | TextJson;
598
+ }[], sourceId?: number) => Promise<{
599
+ applied: boolean;
600
+ }>;
581
601
  };
582
602
  /** Chapters and the A-B loop of the current media. */
583
603
  marks: {
@@ -666,7 +686,10 @@ export declare function createWire(transport: Transport): {
666
686
  };
667
687
  /** `text` is a template in the plugin's catalog, formatted with `args` (player 0.4.3). */
668
688
  osd: {
669
- show: (text: TextValueJson, args?: TemplateArgsJson) => Promise<void>;
689
+ show: (text: TextValueJson, args?: TemplateArgsJson, actions?: {
690
+ title: TextValueJson;
691
+ command: string;
692
+ }[]) => Promise<void>;
670
693
  panel: (text: TextValueJson | null, args?: TemplateArgsJson, maxColumns?: number) => Promise<void>;
671
694
  };
672
695
  layout: {
@@ -775,6 +798,11 @@ export declare function createWire(transport: Transport): {
775
798
  } | {
776
799
  kind: "rawMp4" | "processedMp4";
777
800
  end: number;
801
+ encoding?: import("./media.ts").WireEncoding;
802
+ } | {
803
+ kind: "rawCopy";
804
+ end: number;
805
+ container?: "auto" | "mp4" | "mkv";
778
806
  })) => Promise<{
779
807
  jobId: number;
780
808
  status: "queued";
package/dist/wire.js CHANGED
@@ -156,6 +156,11 @@ export function createWire(transport) {
156
156
  marks,
157
157
  ...(sourceId === undefined ? {} : { sourceId }),
158
158
  }),
159
+ /** Player 0.7.6. */
160
+ setRanges: (ranges, sourceId) => transport.request("timeline.setRanges", {
161
+ ranges,
162
+ ...(sourceId === undefined ? {} : { sourceId }),
163
+ }),
159
164
  },
160
165
  /** Chapters and the A-B loop of the current media. */
161
166
  marks: {
@@ -245,9 +250,11 @@ export function createWire(transport) {
245
250
  },
246
251
  /** `text` is a template in the plugin's catalog, formatted with `args` (player 0.4.3). */
247
252
  osd: {
248
- show: (text, args) => transport.request("osd.show", {
253
+ show: (text, args, actions) => transport.request("osd.show", {
249
254
  text,
250
255
  ...(args === undefined ? {} : { args }),
256
+ // Player 0.7.6; older players ignore it.
257
+ ...(actions === undefined ? {} : { actions }),
251
258
  }),
252
259
  panel: (text, args, maxColumns) => transport.request("osd.panel", {
253
260
  text,
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.6.8: 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. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
3
+ > @vestra-player/sdk 1.6.10: 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. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
4
4
 
5
5
  ## Read the documentation
6
6
 
@@ -29,14 +29,14 @@
29
29
  ## API
30
30
 
31
31
  - `/API/Commands`: 15 entries: CommandState, Bindable, Condition, When, MenuId, BarButtonIcon, CommandButton, CommandOptions, Command, Commands, HostAction, Host, BindingRef, BindingInfo, Bindings
32
- - `/API/Media`: 6 entries: CaptureOptions, Snapshot, ExportOptions, JobState, ExportJob, MediaCapture
32
+ - `/API/Media`: 7 entries: CaptureOptions, Snapshot, ExportOptions, ExportEncoding, JobState, ExportJob, MediaCapture
33
33
  - `/API/Packages`: 10 entries: PackageManifest, PackageSource, InstallOptions, PackageCandidate, PackageChangeStatus, PackageChange, PluginInfo, PluginDisplay, PluginWarning, Packages
34
- - `/API/Playback`: 18 entries: MarkKind, TimelineMark, TimelineLayer, Timeline, AudioTrack, PlayerState, AudioDevice, Chapter, Conditional, Media, Scrub, StreamFrame, StreamOptions, StreamStartError, Stream,…
34
+ - `/API/Playback`: 20 entries: MarkKind, TimelineMark, RangeStyle, TimelineRange, TimelineLayer, Timeline, AudioTrack, PlayerState, AudioDevice, Chapter, Conditional, Media, Scrub, StreamFrame, StreamOptions,…
35
35
  - `/API/Plugin`: 13 entries: Disposable, PluginError, Scope, Plugin, StopReason, DisposeInfo, WindowState, MainWindow, AppInfo, App, SystemInfo, DisplayInfo, Json
36
36
  - `/API/Processing`: 18 entries: FilterStatus, VideoFilter, AudioFilter, ViewSettings, VideoView, RenderSettings, RenderOptions, VideoShaders, UpscalingReasonCode, UpscalingReason, UpscalingDecodingCause,…
37
37
  - `/API/Services`: 15 entries: Clipboard, Power, ToolResult, Tools, JsonStore, Storage, SessionStorage, System, Secrets, RenderingMode, RenderingPreference, RenderingReasonCode, RenderingReason, RenderingState,…
38
38
  - `/API/Sources`: 7 entries: ResolverOptions, WebResolverOptions, FileResolverOptions, ResolvedStream, Resolver, Resolvers, ResolvedMedia
39
39
  - `/API/Subtitles`: 5 entries: SubtitleTrack, SubtitlesState, SubtitleConditional, Subtitles, SubtitleStyle
40
- - `/API/UI`: 42 entries: ImageRef, ImageOptions, MarkdownRef, MarkdownOptions, PropValue, PropData, Props, Ui, View, Window, Windows, SlotState, SlotPanel, PanelState, PanelSlot, SidebarSlot, Panel,…
41
- - `/API/Testing`: 32 entries: CapabilityInfo, CAPABILITY_INFO, MESSAGE_LIMIT, COMMAND_LIMITS, MENU_IDS, BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BuiltinSubmenu, BUILTIN_SUBMENUS, BuiltinCommand, BUILTIN_COMMANDS, LOCALES,…
40
+ - `/API/UI`: 44 entries: ImageRef, ImageOptions, MarkdownRef, MarkdownOptions, PropValue, PropData, Props, Ui, View, Window, Windows, SlotState, SlotPanel, PanelState, PanelSlot, SidebarSlot, Panel,…
41
+ - `/API/Testing`: 33 entries: CapabilityInfo, CAPABILITY_INFO, MESSAGE_LIMIT, COMMAND_LIMITS, MENU_IDS, BAR_BUTTON_ICONS, BAR_BUTTON_LIMITS, BuiltinSubmenu, BUILTIN_SUBMENUS, BuiltinCommand, BUILTIN_COMMANDS, LOCALES,…
42
42
  - `/API/Docs`: 4 entries: DOCS_PAGE_BYTES, DocsOptions, docs, searchDocs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.6.8",
3
+ "version": "1.6.10",
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": {