polite-media 0.3.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.
- package/CHANGELOG.md +113 -0
- package/LICENSE +21 -0
- package/README.md +675 -0
- package/dist/coordinator.d.ts +231 -0
- package/dist/coordinator.d.ts.map +1 -0
- package/dist/coordinator.js +1017 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/env.d.ts +30 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +49 -0
- package/dist/env.js.map +1 -0
- package/dist/events.d.ts +70 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +42 -0
- package/dist/events.js.map +1 -0
- package/dist/image.css +1 -0
- package/dist/image.d.ts +45 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +127 -0
- package/dist/image.js.map +1 -0
- package/dist/layer.css +1 -0
- package/dist/reveal.d.ts +34 -0
- package/dist/reveal.d.ts.map +1 -0
- package/dist/reveal.js +72 -0
- package/dist/reveal.js.map +1 -0
- package/dist/sources.d.ts +22 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +148 -0
- package/dist/sources.js.map +1 -0
- package/dist/targets.d.ts +20 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +17 -0
- package/dist/targets.js.map +1 -0
- package/dist/video.css +1 -0
- package/dist/video.d.ts +15 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +17 -0
- package/dist/video.js.map +1 -0
- package/dist/warm.d.ts +13 -0
- package/dist/warm.d.ts.map +1 -0
- package/dist/warm.js +12 -0
- package/dist/warm.js.map +1 -0
- package/dist/warming.d.ts +62 -0
- package/dist/warming.d.ts.map +1 -0
- package/dist/warming.js +136 -0
- package/dist/warming.js.map +1 -0
- package/package.json +96 -0
- package/src/coordinator.ts +1337 -0
- package/src/env.ts +56 -0
- package/src/events.ts +78 -0
- package/src/image.css +74 -0
- package/src/image.ts +160 -0
- package/src/layer.css +60 -0
- package/src/reveal.ts +75 -0
- package/src/sources.ts +164 -0
- package/src/targets.ts +27 -0
- package/src/video.css +74 -0
- package/src/video.ts +32 -0
- package/src/warm.ts +12 -0
- package/src/warming.ts +162 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { type Target } from './targets.js';
|
|
2
|
+
/**
|
|
3
|
+
* @module
|
|
4
|
+
* The single arbiter of what plays. Nothing outside this module calls `play()`
|
|
5
|
+
* or `pause()`, so "why is this video running" always has one answer.
|
|
6
|
+
*
|
|
7
|
+
* The `@module` tag matters: without it TypeScript attaches this preamble to the
|
|
8
|
+
* first declaration below, and a consumer hovering that symbol gets a sentence
|
|
9
|
+
* about internal call discipline instead of its own documentation.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Deliberately three cases rather than a number. These are the ones that exist,
|
|
13
|
+
* the type system does the checking a bare `number` would invite (`0.5`), and it
|
|
14
|
+
* avoids inventing an answer to which of several incumbents a rival displaces --
|
|
15
|
+
* the incumbent's hysteresis has clean semantics only for a single slot.
|
|
16
|
+
*/
|
|
17
|
+
export type AtOnce = 0 | 1 | 'all';
|
|
18
|
+
/**
|
|
19
|
+
* How patient a video is about starting, as a genuine ladder: each value waits
|
|
20
|
+
* for everything the one before it did, and then something more.
|
|
21
|
+
*
|
|
22
|
+
* Buffering is deliberately not a fourth value here. It is a different question
|
|
23
|
+
* and lives on {@link ConfigureOptions.requireBuffered}, so the two compose.
|
|
24
|
+
*/
|
|
25
|
+
export type StartWhen = 'visible' | 'page-loaded' | 'interaction';
|
|
26
|
+
/**
|
|
27
|
+
* Options for {@link configure}. Every field is optional; anything left out keeps
|
|
28
|
+
* its default.
|
|
29
|
+
*
|
|
30
|
+
* All-optional deliberately. As a fully required interface this could not be used
|
|
31
|
+
* for what a consumer naturally reaches for -- `const preset: ConfigureOptions =
|
|
32
|
+
* { atOnce: 0 }` -- and only the inline `configure({ ... })` form worked, via
|
|
33
|
+
* contextual typing.
|
|
34
|
+
*/
|
|
35
|
+
export interface ConfigureOptions {
|
|
36
|
+
/**
|
|
37
|
+
* How far outside the viewport a video starts buffering, so it is ready by the
|
|
38
|
+
* time it arrives. `'200px'` on a feed is the difference between a card that
|
|
39
|
+
* plays as it lands and one that shows its poster first.
|
|
40
|
+
*
|
|
41
|
+
* Defaults to `'0px'`: buffering video a visitor may never scroll to is the
|
|
42
|
+
* opposite of what this package is for, and on a twelve-card grid it would
|
|
43
|
+
* fetch twelve files.
|
|
44
|
+
*
|
|
45
|
+
* This drives a second observer of its own, and deliberately does not touch
|
|
46
|
+
* the thresholds. `intersectionRatio` is measured against the root *including*
|
|
47
|
+
* the margin, so a single observer made every threshold mean less than it
|
|
48
|
+
* said: measured on a 368px card at a 50px margin, 25% on screen reported 0.39
|
|
49
|
+
* and `pauseBelow: 0.25` actually stopped the video at about 10% visible, with
|
|
50
|
+
* the error scaling by element height. The observer that decides playback
|
|
51
|
+
* keeps no margin, so a fraction is always the true visible fraction.
|
|
52
|
+
*/
|
|
53
|
+
prefetchMargin?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Which viewports count as small. Configurable because 767px is one project's
|
|
56
|
+
* breakpoint, not a fact about phones.
|
|
57
|
+
*/
|
|
58
|
+
smallViewport?: string;
|
|
59
|
+
/**
|
|
60
|
+
* How many videos may run at once.
|
|
61
|
+
*
|
|
62
|
+
* - `'all'` lets every visible video play. A bento grid of cards whose content
|
|
63
|
+
* *is* the video is meant to move.
|
|
64
|
+
* - `1` gives one video the screen at a time, the rest holding their posters.
|
|
65
|
+
* A feed wants this: the eye has one subject, and the handover happens as
|
|
66
|
+
* the next card takes the slot.
|
|
67
|
+
* - `0` never starts a video, spending nothing on data or battery.
|
|
68
|
+
*
|
|
69
|
+
* Pass an object to split the answer by viewport, which is the default:
|
|
70
|
+
* `{ small: 1, large: 'all' }`. Phones have far less decode headroom than
|
|
71
|
+
* desktops -- three concurrent H.264 streams while compositing drops frames
|
|
72
|
+
* badly on real hardware -- so they arbitrate while a desktop does not.
|
|
73
|
+
* {@link ConfigureOptions.smallViewport} decides which side a viewport is on.
|
|
74
|
+
*
|
|
75
|
+
* No standards or platform source recommends any of these; the default is the
|
|
76
|
+
* one with device testing behind it.
|
|
77
|
+
*/
|
|
78
|
+
atOnce?: AtOnce | {
|
|
79
|
+
small: AtOnce;
|
|
80
|
+
large: AtOnce;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Visible fraction, 0 to 1, at or below which a video stops.
|
|
84
|
+
*
|
|
85
|
+
* Defaults to `0.5`: a video runs while it is the thing you are looking at and
|
|
86
|
+
* stops once it is mostly gone. At `0` it stopped only when entirely off
|
|
87
|
+
* screen, so one hanging on by a sliver effectively never stopped.
|
|
88
|
+
*
|
|
89
|
+
* **This caps how tall a managed video can be.** `intersectionRatio` is a
|
|
90
|
+
* fraction of the *element*, and with no margin on the playback observer its
|
|
91
|
+
* ceiling is `viewport / height` -- so anything taller than twice the viewport
|
|
92
|
+
* can never reach `0.5` and would never play. Measured at a 953px viewport:
|
|
93
|
+
* 1.5x viewport height peaks at 0.667 and 3x at 0.333. Lower it for a tall
|
|
94
|
+
* video, or shorten the box.
|
|
95
|
+
*/
|
|
96
|
+
pauseBelow?: number;
|
|
97
|
+
/**
|
|
98
|
+
* How patient a video is about starting. Each rung is strictly more patient
|
|
99
|
+
* than the last.
|
|
100
|
+
*
|
|
101
|
+
* - `'visible'` starts fetching the moment the video is on screen. Module
|
|
102
|
+
* scripts are deferred, so on a real page that lands inside the tail of the
|
|
103
|
+
* page's own loading and competes with it.
|
|
104
|
+
* - `'page-loaded'` waits for `window`'s `load` event first, so the video
|
|
105
|
+
* competes with nothing the page still needs. The default.
|
|
106
|
+
* - `'interaction'` additionally waits for the visitor: the first
|
|
107
|
+
* `pointerdown`, `keydown` or `scroll`.
|
|
108
|
+
*
|
|
109
|
+
* `'page-loaded'` is the default because a video that never plays reads as
|
|
110
|
+
* broken. It keeps the part that matters most: the fetch happens after `load`,
|
|
111
|
+
* so those bytes never compete with the page's own.
|
|
112
|
+
*
|
|
113
|
+
* **Reach for `'interaction'` when Largest Contentful Paint matters.** The
|
|
114
|
+
* browser stops updating LCP on "a tap, scroll, or keypress"
|
|
115
|
+
* (https://web.dev/articles/lcp), so a video revealed after that signal can
|
|
116
|
+
* never become the LCP element, and a synthetic audit, which never interacts,
|
|
117
|
+
* never starts it at all. The cost is a visitor who lands and never scrolls,
|
|
118
|
+
* taps or types: they see a still.
|
|
119
|
+
*
|
|
120
|
+
* Below the fold the choice barely matters, since a video down there cannot be
|
|
121
|
+
* seen without scrolling and scrolling is the interaction. It is a policy for
|
|
122
|
+
* whatever is on screen at load, which in practice means the hero.
|
|
123
|
+
*
|
|
124
|
+
* Two things compose with this rather than replacing it.
|
|
125
|
+
* {@link ConfigureOptions.requireBuffered} asks for data as well as patience,
|
|
126
|
+
* and `until` gates one video on your own promise while this is the policy for
|
|
127
|
+
* all of them. A video waits for every gate that applies to it.
|
|
128
|
+
*/
|
|
129
|
+
startWhen?: StartWhen;
|
|
130
|
+
/**
|
|
131
|
+
* Hold playback until the video can play through without stalling.
|
|
132
|
+
*
|
|
133
|
+
* Separate from {@link ConfigureOptions.startWhen} because they answer
|
|
134
|
+
* different questions: that one is *when may it begin*, this one is *how much
|
|
135
|
+
* data first*. As a fourth `startWhen` value it competed with `'interaction'`,
|
|
136
|
+
* so "wait for the user, and also wait for the buffer" could not be said at
|
|
137
|
+
* all.
|
|
138
|
+
*
|
|
139
|
+
* Raises `preload` to `'auto'` when it prepares, which it has to: the markup
|
|
140
|
+
* contract says `preload="none"`, and a browser buffers nothing until playback
|
|
141
|
+
* is asked for, so waiting for `canplaythrough` without the promotion would
|
|
142
|
+
* wait forever.
|
|
143
|
+
*/
|
|
144
|
+
requireBuffered?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Call before the first `register`.
|
|
148
|
+
*
|
|
149
|
+
* `atOnce`, `startWhen` and `requireBuffered` are read on every reconcile, so
|
|
150
|
+
* they can be changed at any time and take effect on the next pass. The three
|
|
151
|
+
* keys in {@link CONSTRUCTION_TIME_KEYS} cannot, and throw if patched while
|
|
152
|
+
* videos are registered. Unregister everything first, or configure earlier.
|
|
153
|
+
*/
|
|
154
|
+
export declare function configure(patch: ConfigureOptions): void;
|
|
155
|
+
export interface RegisterOptions {
|
|
156
|
+
/**
|
|
157
|
+
* Hold this video out of the arbiter's reach until the promise settles. A hero
|
|
158
|
+
* at scroll-top is reported visible in the observer's very first batch, so
|
|
159
|
+
* without a gate it starts before whatever the page is waiting on (a splash
|
|
160
|
+
* screen, a consent dialog) has finished.
|
|
161
|
+
*/
|
|
162
|
+
until?: Promise<unknown>;
|
|
163
|
+
/**
|
|
164
|
+
* Element to observe instead of the video, for when the video is absolutely
|
|
165
|
+
* positioned inside a wrapper that carries the real layout box.
|
|
166
|
+
*/
|
|
167
|
+
observe?: Element;
|
|
168
|
+
/**
|
|
169
|
+
* Override the page's {@link ConfigureOptions.startWhen} for this video.
|
|
170
|
+
*
|
|
171
|
+
* A page usually wants one policy, but not always: only a video that can be
|
|
172
|
+
* the LCP element needs the strictest gate, and holding a below-fold grid to
|
|
173
|
+
* the same rule buys nothing.
|
|
174
|
+
*/
|
|
175
|
+
startWhen?: StartWhen;
|
|
176
|
+
}
|
|
177
|
+
export declare function reconcile(): void;
|
|
178
|
+
/**
|
|
179
|
+
* Starts managing a video: reveals it on its first genuinely painted frame,
|
|
180
|
+
* plays it only while it is visible, falls through its `<source>` list when one
|
|
181
|
+
* cannot be decoded, and stops it when a gate closes.
|
|
182
|
+
*
|
|
183
|
+
* The video and its poster must already share a box carrying `data-polite-media`
|
|
184
|
+
* in the authored markup, and the video should be `muted loop playsinline
|
|
185
|
+
* preload="none"`. Calling this twice on the same element is a no-op.
|
|
186
|
+
*
|
|
187
|
+
* @param video the element to manage
|
|
188
|
+
* @param options see {@link RegisterOptions}
|
|
189
|
+
*/
|
|
190
|
+
export declare function register(video: HTMLVideoElement, options?: RegisterOptions): void;
|
|
191
|
+
/** Anything that names one or more videos. See {@link Target}. */
|
|
192
|
+
export type VideoTarget = Target<HTMLVideoElement>;
|
|
193
|
+
/**
|
|
194
|
+
* Registers every video a target names, so the common case is one line and
|
|
195
|
+
* matches `revealImages` on the image side rather than being a second idea.
|
|
196
|
+
*
|
|
197
|
+
* `observe` is deliberately not accepted. Each observed element maps to exactly
|
|
198
|
+
* one entry, so handing the same wrapper to several videos would silently
|
|
199
|
+
* discard all but the last. Anything needing it, or a different gate per video,
|
|
200
|
+
* goes through {@link register} one at a time.
|
|
201
|
+
*
|
|
202
|
+
* Idempotent, because `register` is: safe to call on every navigation of a
|
|
203
|
+
* client-side router, where module scripts do not re-run.
|
|
204
|
+
*/
|
|
205
|
+
export declare function registerAll(target: VideoTarget, options?: Omit<RegisterOptions, 'observe'>): void;
|
|
206
|
+
/**
|
|
207
|
+
* Stops managing a video and releases everything it owned: the observer entry,
|
|
208
|
+
* any pending pause timer, its listeners, and the page-level listeners once it
|
|
209
|
+
* was the last one. Safe to call for a video that was never registered.
|
|
210
|
+
*/
|
|
211
|
+
export declare function unregister(video: HTMLVideoElement): void;
|
|
212
|
+
export declare function pauseAll(): void;
|
|
213
|
+
/** Lets playback resume, undoing {@link pauseAll}. */
|
|
214
|
+
export declare function resumeAll(): void;
|
|
215
|
+
/**
|
|
216
|
+
* Releases every video, for a host tearing down the whole page. Configuration
|
|
217
|
+
* survives: it describes the page's setup rather than the videos currently on
|
|
218
|
+
* it, and a client-side router calling this per navigation would otherwise have
|
|
219
|
+
* its settings quietly reverted on the first swap. The once-per-page warnings do
|
|
220
|
+
* reset, because the markup they judge is about to be replaced.
|
|
221
|
+
*/
|
|
222
|
+
export declare function unregisterAll(): void;
|
|
223
|
+
/** Internal reset for tests. Not exported from the package entry point. */
|
|
224
|
+
export declare function resetForTests(): void;
|
|
225
|
+
/** Internal view for tests. Not exported from the package entry point. */
|
|
226
|
+
export declare function inspect(): {
|
|
227
|
+
tracked: number;
|
|
228
|
+
observing: boolean;
|
|
229
|
+
lifecycle: boolean;
|
|
230
|
+
};
|
|
231
|
+
//# sourceMappingURL=coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../src/coordinator.ts"],"names":[],"mappings":"AAUA,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3D;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4ED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAcvD;AAwDD,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAqhBD,wBAAgB,SAAS,IAAI,IAAI,CAsFhC;AAgLD;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,GAAE,eAAoB,GAAG,IAAI,CAoDrF;AAED,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAM,GAC7C,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAuBxD;AAsDD,wBAAgB,QAAQ,IAAI,IAAI,CAG/B;AAED,sDAAsD;AACtD,wBAAgB,SAAS,IAAI,IAAI,CAGhC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAOpC;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,IAAI,IAAI,CAQpC;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAErF"}
|