narraleaf-react 0.45.1 → 0.47.0

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.
@@ -6,4 +6,5 @@ export * from "./types";
6
6
  export * from "./position";
7
7
  export * from "./transition";
8
8
  export * from "./interface";
9
+ export type { PreloadAcquisition, PreloadBand, PreloadEntry, PreloadMoment, PreloadPlan, PreloadResource, PreloadResourceType, PreloadStrategy, } from "../preload/types";
9
10
  export { i, c, b };
@@ -6,4 +6,5 @@ import { usePathname, useParams, useQueryParams } from "../../player/lib/PageRou
6
6
  export * from "../../player/type";
7
7
  export * from "../../player/libElements";
8
8
  export type { ImageCacheManager, ImageCacheStats } from "../../player/lib/ImageCacheManager";
9
+ export { createDefaultPreloadStrategy } from "../../player/elements/preload/defaultStrategy";
9
10
  export { GameProviders, Player, useGame, useRouter, usePathname, useParams, useQueryParams, };
@@ -11,6 +11,7 @@ import type { GameElementHistory } from "./action/gameHistory";
11
11
  import { MenuComponent, NotificationComponent, NvlDialogComponent, SayComponent } from "./common/player";
12
12
  import { LiveGameEventToken } from "./types";
13
13
  import type { AudioBusDeclaration } from "./game/audioBus";
14
+ import type { PreloadStrategy } from "./preload/types";
14
15
  /**
15
16
  * Current save format version.
16
17
  *
@@ -168,10 +169,30 @@ export type GameConfig = {
168
169
  * @default 50
169
170
  */
170
171
  ratioUpdateInterval: number;
172
+ /**
173
+ * Who decides what the player warms, when, and where the bytes come from.
174
+ *
175
+ * Unset, the player uses its built-in strategy, which is the behaviour it has always had: walk
176
+ * the action tree of the scene about to paint and of the scenes reachable from it, split the
177
+ * result into a first frame, the scene's registered set and a look-ahead, and fetch each one
178
+ * into an object url with an off-screen decode. Every `preload*` field below steers that
179
+ * strategy and nothing else.
180
+ *
181
+ * Set it and the player stops guessing. It asks this object what should be warm at each moment
182
+ * in the story and does exactly that, and if the object also supplies
183
+ * {@link PreloadStrategy.acquire} it stops fetching too - which is what lets a host whose assets
184
+ * are already on local disk hand back the url it was given and keep no second copy in memory.
185
+ * The cache, its budgets and the pins are still the player's; the plan is not.
186
+ *
187
+ * See {@link PreloadStrategy}.
188
+ */
189
+ preload?: PreloadStrategy;
171
190
  /**
172
191
  * The game will preload the image with this delay between each preload task
173
192
  *
174
- * A single preload task may contain {@link GameConfig.preloadConcurrency} images
193
+ * A single preload task may contain {@link GameConfig.preloadConcurrency} images.
194
+ * Read by the built-in strategy's speculative band only; a game with its own
195
+ * {@link GameConfig.preload} paces that band with the same field.
175
196
  * @default 100
176
197
  */
177
198
  preloadDelay: number;
@@ -0,0 +1,213 @@
1
+ import type { Scene } from "../elements/scene";
2
+ import type { Sound } from "../elements/sound";
3
+ import type { Story } from "../elements/story";
4
+ import type { Video } from "../elements/video";
5
+ /**
6
+ * The preload seam: what the player warms, when it warms it, and where the bytes come from.
7
+ *
8
+ * ## Why this is a seam and not a policy
9
+ *
10
+ * The player used to answer three questions on its own. *What will be needed* it guessed, by
11
+ * walking the action tree of the scene about to paint and of every scene reachable from it. *When*
12
+ * followed from that walk: one pass per scene, split into a first frame, the scene's whole
13
+ * registered set, and a look-ahead. *How* was fixed: fetch the bytes, mint an object url, decode
14
+ * the image off-screen, hold the bitmap under a budget.
15
+ *
16
+ * All three answers are wrong for a host that knows more than the walk can see. A tool that
17
+ * compiled the story knows exactly which row shows which asset, in which order, and how big each
18
+ * one is; the walk only knows what a scene mentions anywhere in it, which on a real project is
19
+ * most of the library - so the pass fetched and decoded a chapter's artwork in order to paint one
20
+ * background. A host serving assets off local disk has no use for the object url either: the file
21
+ * is already there, and copying it into a blob costs that memory a second time.
22
+ *
23
+ * So the player now asks instead of deciding. A {@link PreloadStrategy} answers *what* and *when*
24
+ * as a {@link PreloadPlan}, and may answer *how* as well through
25
+ * {@link PreloadStrategy.acquire}. Everything the player still owns - the cache, its budgets, what
26
+ * a mounted element pins, and the url an element is finally pointed at - is unchanged, which is
27
+ * what lets a strategy replace the plan without replacing the player.
28
+ *
29
+ * A game that supplies no strategy gets the built-in one, which reproduces the walk and the tiers
30
+ * exactly as they were, driven by the same `preload*` fields of `GameConfig`.
31
+ */
32
+ /**
33
+ * What kind of thing a preload resource names.
34
+ *
35
+ * Only images are named this way in a plan's {@link PreloadPlan.entries}: they are the resources
36
+ * the player fetches, caches and decodes, so a url is the whole of what it needs to know. Audio and
37
+ * video are named by element instead ({@link PreloadPlan.audio}, {@link PreloadPlan.video}),
38
+ * because what warming them means is a property of the element and not of the url. `video` survives
39
+ * here for {@link PreloadStrategy.onMissing} and {@link PreloadStrategy.acquire}, which speak about
40
+ * a resource rather than about a plan.
41
+ */
42
+ export type PreloadResourceType = "image" | "video";
43
+ /** One thing a plan can ask for, named by the url the stage will show. */
44
+ export type PreloadResource = {
45
+ type: PreloadResourceType;
46
+ /**
47
+ * The url the stage points an element at, which is also the key the cache stores it under.
48
+ *
49
+ * A strategy that rewrites urls - a host serving through its own protocol, say - must name the
50
+ * resource by the url the *stage* will use and do the rewriting inside
51
+ * {@link PreloadStrategy.acquire}. The two are the same string for every ordinary game.
52
+ */
53
+ src: string;
54
+ };
55
+ /**
56
+ * How urgently a resource is wanted. One axis, three points, and only the first of them blocks.
57
+ *
58
+ * - `gate` - the frame is not allowed to paint until this has landed. That is the loading screen's
59
+ * whole meaning, so a plan that puts a chapter in this band is a plan that opens late.
60
+ * - `soon` - start now, at full speed, but nothing waits on it: what the player is a click away
61
+ * from needing.
62
+ * - `idle` - speculative. Paced by `GameConfig.preloadDelay`, run after the other two, and
63
+ * abandoned without ceremony when the moment is superseded.
64
+ */
65
+ export type PreloadBand = "gate" | "soon" | "idle";
66
+ export type PreloadEntry = {
67
+ /**
68
+ * Always an image.
69
+ *
70
+ * Video used to be nameable here, and warming it meant reading the bytes once and dropping
71
+ * them, in the hope that whatever served them kept a copy. That hope is unfounded on the hosts
72
+ * that matter - a game serving assets through its own protocol has no cache to warm - so it
73
+ * reported warming that had not happened. {@link PreloadPlan.video} is the answer that works.
74
+ */
75
+ type: "image";
76
+ src: string;
77
+ band: PreloadBand;
78
+ /**
79
+ * Whether to decode the image off-screen before anything shows it, and hold the bitmap.
80
+ *
81
+ * A decode is what lets an image paint on the frame it is revealed on rather than a frame or
82
+ * two later, and it is the expensive half of warming one: measured over a real library,
83
+ * fetching every image took 473 ms and fetching *and* decoding them took 2,140 ms, with each
84
+ * retained bitmap costing width x height x 4 bytes for as long as it is held. So it is worth
85
+ * paying for what is about to be revealed and wasteful for what merely might be.
86
+ *
87
+ * Defaults to true on the `gate` and `soon` bands and false on `idle`.
88
+ */
89
+ decode?: boolean;
90
+ };
91
+ /**
92
+ * What should be warm at one moment in the story, and what may be forgotten.
93
+ *
94
+ * A plan is complete: it replaces the previous one rather than adding to it. That is what makes
95
+ * {@link PreloadPlan.keep} meaningful - a scene the story has left keeps nothing, and its artwork
96
+ * is released as soon as no element is still showing it.
97
+ */
98
+ export type PreloadPlan = {
99
+ /** Every image and video this moment wants, in the order each band should warm them. */
100
+ readonly entries: readonly PreloadEntry[];
101
+ /**
102
+ * Sounds the audio cache should hold for this moment, and only these.
103
+ *
104
+ * Separate from {@link PreloadPlan.entries}, and named by element rather than by url, because
105
+ * audio is warmed by a different cache with a different budget, and whether a clip is decoded
106
+ * into memory or streamed as it plays is a property of the sound rather than of its url. It is
107
+ * also never gated on: the audio context stays locked until the page has been interacted with,
108
+ * so a loading screen that waited for a clip could wait for ever.
109
+ */
110
+ readonly audio?: readonly Sound[];
111
+ /**
112
+ * Clips this moment wants buffering, nearest first, and only these.
113
+ *
114
+ * Named by element for the same reason audio is, only more so: warming a video means putting
115
+ * its element in the document and letting the browser buffer into it, and the element that
116
+ * buffered has to be the one that plays or nothing was gained. There is no video cache in the
117
+ * player, and there is no url-shaped way to fill one.
118
+ *
119
+ * The order is a statement of what is coming first. The player admits the clips one at a time
120
+ * and starts the next when the current one reports it can play, so how many are really being
121
+ * fetched at once follows the connection rather than the length of this list - which is why
122
+ * there is no setting for it and no number for a host to choose. A clip the story has already
123
+ * declared is left alone: it is on the stage on the author's own instruction.
124
+ *
125
+ * Never gated on. A clip can take arbitrarily long to buffer, and a loading screen that waited
126
+ * for one would be a download bar wearing a story's clothes. Omit the field to leave the warm
127
+ * set as it is; an empty array releases it.
128
+ */
129
+ readonly video?: readonly Video[];
130
+ /**
131
+ * Every url the image cache may keep, which is normally the plan's own entries.
132
+ *
133
+ * Anything outside this set is released at once if nothing is showing it, and the moment its
134
+ * last element unmounts otherwise. Omit it to leave what the cache holds alone - which is what
135
+ * a plan that only adds to a scene's warm set wants.
136
+ */
137
+ readonly keep?: readonly string[];
138
+ /**
139
+ * Urls no budget may release, whatever else happens - normally the opening frame.
140
+ *
141
+ * Whatever a mounted element is showing is protected separately and does not need naming here.
142
+ */
143
+ readonly pin?: readonly string[];
144
+ };
145
+ /** Why the player is asking. A strategy may answer only the moments it cares about. */
146
+ export type PreloadMoment = {
147
+ /** A scene is about to paint, or has just been entered. */
148
+ kind: "scene";
149
+ scene: Scene;
150
+ story: Story | null;
151
+ } | {
152
+ /**
153
+ * The story advanced. Sent for every action, so a strategy that plans row by row answers here
154
+ * and one that plans per scene returns null.
155
+ */
156
+ kind: "advance";
157
+ actionId: string | null;
158
+ scene: Scene | null;
159
+ story: Story | null;
160
+ };
161
+ /**
162
+ * Where the bytes for one resource came from, and what keeping them costs.
163
+ *
164
+ * Returned by {@link PreloadStrategy.acquire}. A host that serves assets from local disk should
165
+ * hand back the url it was given with `bytes: 0` and no `release`: the browser then fetches and
166
+ * caches the file once, the way it would for any other url on the page, and the player holds no
167
+ * second copy of it in the renderer's heap.
168
+ */
169
+ export type PreloadAcquisition = {
170
+ /** The url an element should be pointed at. May be the resource's own url. */
171
+ url: string;
172
+ /**
173
+ * What holding this costs the player's fetched-bytes budget. Zero when the host owns the
174
+ * memory, which is the honest answer for a url the player copied nothing for.
175
+ */
176
+ bytes?: number;
177
+ /** Called once when the player lets the entry go, for a url the host has to clean up. */
178
+ release?: () => void;
179
+ };
180
+ /**
181
+ * The host's answer to what the player should warm, and optionally to how.
182
+ *
183
+ * Supplied as `GameConfig.preload`. Every method is asked on the player's own schedule; none may
184
+ * assume it is called in order, and all may be called again for the same moment after a reload.
185
+ */
186
+ export interface PreloadStrategy {
187
+ /**
188
+ * What should be warm at this moment, or null to leave the previous plan in force.
189
+ *
190
+ * May be asynchronous: a host that has to ask another process what a scene uses answers when it
191
+ * knows. The player will not paint a gated frame until the plan has arrived and its `gate` band
192
+ * has landed, so a strategy that takes its time is a strategy that opens late - the same trade
193
+ * the built-in one makes, only visible.
194
+ */
195
+ plan(moment: PreloadMoment): PreloadPlan | null | Promise<PreloadPlan | null>;
196
+ /**
197
+ * Obtain the url for one resource, replacing the player's own fetch.
198
+ *
199
+ * Omit it and the player fetches the resource itself and mints an object url, which is what it
200
+ * has always done. Return null to say "nothing to warm, show it directly": the player then
201
+ * points the element at the resource's own url and caches nothing for it.
202
+ */
203
+ acquire?(resource: PreloadResource, signal: AbortSignal): Promise<PreloadAcquisition | null>;
204
+ /**
205
+ * Told when the stage shows something no plan named.
206
+ *
207
+ * This is the case the player used to report by asking the author, in a console warning, to
208
+ * register the image by hand. A host that plans from a compiled story can say something far
209
+ * more useful - which row shows it - so the player hands the fact over rather than guessing at
210
+ * the remedy. Called at most once per url.
211
+ */
212
+ onMissing?(resource: PreloadResource): void;
213
+ }
@@ -0,0 +1,16 @@
1
+ import type { Game } from "../../../nlcore/game";
2
+ import type { PreloadStrategy } from "../../../nlcore/preload/types";
3
+ /**
4
+ * The strategy a game gets when it supplies none: the walk and the tiers exactly as they were.
5
+ *
6
+ * It exists for two reasons beyond compatibility. It is the only proof that the seam is wide
7
+ * enough - if the behaviour the player shipped for years cannot be expressed as a
8
+ * {@link PreloadPlan}, the seam is the wrong shape. And it is what a host can fall back to for the
9
+ * parts of a story it does not know about, since a strategy is free to call this one and merge.
10
+ *
11
+ * Everything it does is read off `GameConfig`, so the fields that used to steer the player directly
12
+ * now steer this: `preloadAllImages` chooses between the two passes below, `preloadGate` decides
13
+ * whether the scene's whole registered set blocks the first frame or only its opening background,
14
+ * and `maxPreloadActions` sizes the prediction window.
15
+ */
16
+ export declare function createDefaultPreloadStrategy(game: Game): PreloadStrategy;
@@ -26,6 +26,7 @@ import { Vfx, VfxStateRaw } from "../nlcore/elements/vfx";
26
26
  import { Timelines } from "./Tasks";
27
27
  import { Notification, NotificationManager } from "./lib/notification";
28
28
  import type { ImageCacheManager } from "./lib/ImageCacheManager";
29
+ import type { PreloadResource } from "../nlcore/preload/types";
29
30
  import { ActionHistoryManager } from "../../game/nlcore/action/actionHistory";
30
31
  import { GameHistoryManager } from "../../game/nlcore/action/gameHistory";
31
32
  import type { Character } from "../nlcore/elements/character";
@@ -204,6 +205,14 @@ export declare class GameState {
204
205
  preloadingScene: Scene | null;
205
206
  flushDep: number;
206
207
  rollLock: Lock;
208
+ /**
209
+ * Clips held on the stage, hidden, because a plan said they are coming - never part of a save.
210
+ * See {@link VideoWarmQueue}.
211
+ */
212
+ private readonly videoWarmQueue;
213
+ private videoMissingReporter;
214
+ /** Sources already reported as unwarmed, so one clip in a loop does not report every pass. */
215
+ private readonly reportedUnwarmedVideos;
207
216
  readonly notificationMgr: NotificationManager;
208
217
  readonly events: EventDispatcher<GameStateEvents>;
209
218
  readonly logger: Logger;
@@ -222,8 +231,50 @@ export declare class GameState {
222
231
  get deps(): number;
223
232
  addVideo(video: Video): this;
224
233
  removeVideo(video: Video): this;
234
+ /**
235
+ * Whether the STORY has put this clip on the stage - a declaration row, a show, a play.
236
+ *
237
+ * Deliberately not "is it in the document": a clip the warm queue is holding is mounted and
238
+ * buffering but nothing has shown it, and the two answers are wanted in different places. What
239
+ * a save records, what an undo takes back and what a second declaration skips are all this one;
240
+ * whether an action may talk to the element is {@link GameState.isVideoOnStage}.
241
+ */
225
242
  isVideoAdded(video: Video): boolean;
243
+ /** Whether the clip is in the document at all, warmed or shown - so whether it has a state to talk to. */
244
+ isVideoOnStage(video: Video): boolean;
245
+ /**
246
+ * Every clip the stage renders: the story's own, then whatever the warm queue is holding.
247
+ *
248
+ * The two lists are kept apart rather than merged on insert because only the first is state.
249
+ * A save is a list of what the story put on the stage; a warmed clip is a performance detail of
250
+ * this run of it, and writing it into a save would make a story edit able to invalidate a save
251
+ * that has nothing to do with the change (`fromData` throws on a video id it cannot find).
252
+ */
226
253
  getVideos(): Video[];
254
+ /**
255
+ * Hold exactly these clips buffering ahead of the story, in this order of preference.
256
+ *
257
+ * What a {@link PreloadPlan.video} asks for. The queue starts them one at a time and waits for
258
+ * each to become playable before starting the next, so how many are really being fetched at
259
+ * once follows the connection rather than the plan; see {@link VideoWarmQueue}.
260
+ */
261
+ retainWarmVideos(videos: readonly Video[]): this;
262
+ /**
263
+ * Where to report a clip the story played that no plan named, or null for nowhere.
264
+ *
265
+ * The image half of this lives on the cache manager, which is the thing that would have fetched
266
+ * the image; a video is never fetched by the player, so the report has to come from the action
267
+ * that starts it.
268
+ */
269
+ useVideoMissingReporter(reporter: ((resource: PreloadResource) => void) | null): this;
270
+ /**
271
+ * Note that a clip is about to be shown or played with nothing having warmed it.
272
+ *
273
+ * Once per source, and only when no plan named it and nothing has it on the stage: a clip the
274
+ * author declared a few rows earlier is warm by authorship, and reporting that would be
275
+ * reporting the feature working.
276
+ */
277
+ reportUnwarmedVideo(video: Video): this;
227
278
  addVfx(vfx: Vfx): this;
228
279
  removeVfx(vfx: Vfx): this;
229
280
  isVfxAdded(vfx: Vfx): boolean;
@@ -1,5 +1,6 @@
1
1
  import type { Game } from "../../../game/nlcore/game";
2
2
  import type { GameState } from "../gameState";
3
+ import type { PreloadStrategy } from "../../nlcore/preload/types";
3
4
  export type PreloadedToken = {
4
5
  abort: () => void;
5
6
  onFinished: (callback: () => void) => PreloadedToken;
@@ -92,7 +93,37 @@ export declare class ImageCacheManager {
92
93
  private seen;
93
94
  private blobBytes;
94
95
  private decodedBytes;
96
+ /**
97
+ * How the cache gets bytes for a source, when the host would rather it did not fetch them.
98
+ *
99
+ * Installed by the preloader from `GameConfig.preload`. Unset, the cache fetches the url and
100
+ * mints an object url for it, which is what it has always done - and which costs the renderer a
101
+ * second copy of every image on a host whose assets are already on local disk.
102
+ */
103
+ private acquisition;
104
+ /** Where a source nothing warmed is reported, when the host wants to hear about it. */
105
+ private missingReporter;
106
+ /** Sources already reported missing, so one unpredicted image is one report and not one a frame. */
107
+ private reportedMissing;
95
108
  constructor(game: Game);
109
+ /**
110
+ * Install the host's way of obtaining bytes, or clear it.
111
+ *
112
+ * Set once, from the preloader, before anything is warmed. Entries already in the cache keep
113
+ * whatever url they were built with; the cache never re-acquires something it holds.
114
+ */
115
+ useAcquisition(acquire: PreloadStrategy["acquire"] | null): this;
116
+ /** Install the host's ear for sources nothing warmed, or clear it. */
117
+ useMissingReporter(onMissing: PreloadStrategy["onMissing"] | null): this;
118
+ /**
119
+ * Say that the stage is showing `src` and no plan named it. Answers whether the host took it.
120
+ *
121
+ * The player's own answer to this was a console warning telling the author to register the
122
+ * image by hand, which is only useful to someone who writes the story in TypeScript. A host that
123
+ * planned from a compiled story can name the row instead, so it gets first refusal and the
124
+ * warning stays for the games that have no host to ask.
125
+ */
126
+ reportMissing(src: string): boolean;
96
127
  has(name: string): boolean;
97
128
  /**
98
129
  * Whether this source has ever been through the cache, whether or not it still is.
@@ -135,9 +166,14 @@ export declare class ImageCacheManager {
135
166
  * {@link GameConfig.decodedImageBudgetBytes}, until this source leaves the cache. Use it for the
136
167
  * assets that are about to be revealed; leave it off for speculative look-ahead preloading,
137
168
  * whose bitmaps would otherwise pile up in memory.
169
+ * @param options.decode run the off-screen decode at all. Defaults to true. Off, the bytes are
170
+ * obtained and nothing else: measured over a real library, that is the difference between 473
171
+ * and 2,140 milliseconds, and it is the right trade for anything the plan does not expect to be
172
+ * revealed soon. `retainDecoded` implies a decode and overrides this.
138
173
  */
139
174
  preload(gameState: GameState, url: string, options?: {
140
175
  retainDecoded?: boolean;
176
+ decode?: boolean;
141
177
  }): PreloadedToken;
142
178
  abortAll(): void;
143
179
  abort(src: string): void;
@@ -191,7 +227,23 @@ export declare class ImageCacheManager {
191
227
  * the budget is a limit, not a preference, and what is on stage is protected by the pins.
192
228
  */
193
229
  private enforce;
194
- private fetchAndDecode;
230
+ /**
231
+ * Get the bytes for one source and, unless told not to, decode them off-screen.
232
+ *
233
+ * The acquisition step is the host's when one is installed: it may hand back the url unchanged
234
+ * and own the memory itself, which is what a host serving local files should do. Otherwise the
235
+ * cache fetches and mints an object url, and is the thing that has to revoke it.
236
+ */
237
+ private acquireAndDecode;
238
+ /** The bytes for one source, from the host when it has an opinion and by fetching otherwise. */
239
+ private acquire;
240
+ /**
241
+ * Give a url back to whoever owns it: the host that supplied it, or the browser that minted it.
242
+ *
243
+ * One place, because the two are indistinguishable to every caller and getting it wrong leaks a
244
+ * whole image - an object url pins its blob for the lifetime of the document.
245
+ */
246
+ private handBack;
195
247
  /** A cached entry's bitmap, wanted again after the budget let it go or a look-ahead skipped it. */
196
248
  private decodeAgain;
197
249
  private runTask;
@@ -0,0 +1,78 @@
1
+ import type { Video } from "../../nlcore/elements/video";
2
+ export type VideoWarmQueueOptions = {
3
+ /**
4
+ * Whether the story itself has put this clip on the stage.
5
+ *
6
+ * Declared clips are the author's own instruction (`/video`, or a `Video.preload()` call) and
7
+ * are never queued: they are already buffering, they are not subject to the ceiling, and
8
+ * releasing one would take away something the story asked for.
9
+ */
10
+ isDeclared(video: Video): boolean;
11
+ /** Ask the player to re-render the stage, because what is mounted has changed. */
12
+ onChange(): void;
13
+ };
14
+ /**
15
+ * Which clips are held on the stage buffering, and in what order they are started.
16
+ *
17
+ * ## Why a queue and not a number
18
+ *
19
+ * Warming a video is not warming an image. An image is bytes plus a decode, and the player can hold
20
+ * both in a cache under a budget it can measure. A video is a `<video>` element that is in the
21
+ * document: the browser buffers into it, the buffer belongs to that element, and the element that
22
+ * buffered has to be the element that plays or the buffering bought nothing. So "warm this clip"
23
+ * means "mount it hidden, early", and the only questions left are how many and in what order.
24
+ *
25
+ * Both are answered here rather than by whoever wrote the story or configured the game. The host's
26
+ * plan says which clips are coming and in what order (nearest first); this admits them one at a
27
+ * time and starts the next when the current one reports it can play. On a fast local disk that
28
+ * empties the queue almost at once; on a slow connection it keeps a single read running instead of
29
+ * five that finish together and too late. Nobody has to know what number to write, which is the
30
+ * point - it is not a number an author could be expected to get right, and the browser is telling
31
+ * us the answer anyway.
32
+ */
33
+ export declare class VideoWarmQueue {
34
+ private readonly options;
35
+ /** Everything the current plan named, declared clips included - what {@link isPlanned} answers. */
36
+ private planned;
37
+ /** What this queue may mount, in plan order, minus anything the story declared itself. */
38
+ private desired;
39
+ /** Mounted by this queue, hidden and buffering. A subset of {@link desired}, in admission order. */
40
+ private admitted;
41
+ /** The clip whose "can play" the queue is waiting for before it starts another. */
42
+ private pending;
43
+ private pendingTimer;
44
+ constructor(options: VideoWarmQueueOptions);
45
+ /**
46
+ * Take a plan's clips as the complete set this moment wants warm.
47
+ *
48
+ * Total replacement, like every other part of a plan: a clip the new plan does not name is
49
+ * released as soon as this returns, because the scene that wanted it is behind the reader. What
50
+ * the story declared is untouched - releasing that is the story's business, not the plan's.
51
+ */
52
+ retain(next: readonly Video[]): void;
53
+ /**
54
+ * A clip is playable, or has failed trying - either way the queue stops waiting for it.
55
+ *
56
+ * Fed from the player's exposed-state mount, which the video component performs on `canplay`
57
+ * and, for a source that will not load, on `error`. Anything that is not the clip being waited
58
+ * for is ignored, so this can be wired to every state mount there is.
59
+ */
60
+ noteReady(video: unknown): void;
61
+ /**
62
+ * Stop holding a clip the story has taken over.
63
+ *
64
+ * Called when a row declares, shows or plays something this queue had mounted. The element does
65
+ * not move and does not reload - it is the same element, now on the stage for a better reason -
66
+ * so this only stops counting it against the ceiling and frees the queue to start another.
67
+ */
68
+ forget(video: Video): void;
69
+ /** Clips this queue is holding on the stage, hidden. */
70
+ getAdmitted(): readonly Video[];
71
+ /** Whether the current plan named this clip at all, declared or not. */
72
+ isPlanned(video: Video): boolean;
73
+ /** Drop everything, for a player being torn down or reset. */
74
+ clear(): void;
75
+ /** Start the next clip if there is room and nothing is being waited on. Reports whether it did. */
76
+ private admit;
77
+ private clearPending;
78
+ }