@vectoriox/iox-ui 5.74.6 → 5.75.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/fesm2022/vectoriox-iox-ui-media-core.mjs +79 -35
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +6 -6
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/media-core/index.d.ts +35 -8
- package/package.json +1 -1
- package/src/media-core/frames.ts +88 -30
|
@@ -86,9 +86,22 @@ function framePosition(played, count) {
|
|
|
86
86
|
}
|
|
87
87
|
/** On a Video whose pictures are drawing: the poster has done its job and steps aside. */
|
|
88
88
|
const FRAMES_SHOWING_ATTRIBUTE = 'data-iox-frames-showing';
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Below this many pictures a second the film stops following the scroll exactly and lands on its own. A landing scroll
|
|
91
|
+
* slows exponentially, so the last picture or two would take most of a second as one slow dissolve between two places:
|
|
92
|
+
* the subject showing twice, and then settling. Landing at least this fast keeps the end of the move a move.
|
|
93
|
+
*/
|
|
94
|
+
const FILM_LANDING_SPEED = 10;
|
|
95
|
+
/** How fast a landing scroll is assumed to slow (per second): where it will stop is its speed over this. */
|
|
96
|
+
const FILM_LANDING_DECAY = 5;
|
|
97
|
+
/** How quickly the film catches up with a scroll that turned back while it was landing ahead of it (per second). */
|
|
98
|
+
const FILM_CATCH_RATE = 25;
|
|
99
|
+
/** How quickly the film's reading of the scroll's speed follows it (per second). */
|
|
100
|
+
const FILM_SPEED_RATE = 30;
|
|
101
|
+
/** A step this short is the same frame asked twice: it moves nothing and reads no speed. */
|
|
102
|
+
const FILM_SAME_FRAME_SECONDS = 0.002;
|
|
103
|
+
const FILM_MAX_STEP_SECONDS = 0.1;
|
|
104
|
+
const FILM_EPSILON = 1e-6;
|
|
92
105
|
/** How finely two neighbours mix. Finer than the eye reads, and a scroll that moves less than a step draws nothing. */
|
|
93
106
|
const FRAME_BLEND_STEPS = 32;
|
|
94
107
|
/**
|
|
@@ -105,6 +118,42 @@ function frameBlendAt(loaded, position) {
|
|
|
105
118
|
const nearest = nearestLoadedFrame(loaded, Math.round(position));
|
|
106
119
|
return nearest < 0 ? null : { from: nearest, to: nearest, mix: 0 };
|
|
107
120
|
}
|
|
121
|
+
/** Where a film stands before its first step: on the moment asked for, still. */
|
|
122
|
+
function filmAt(wanted) {
|
|
123
|
+
return { position: wanted, wanted, speed: 0, direction: 1 };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One step of the film. Moving fast, it stands exactly where the scroll asks. Landing (slower than FILM_LANDING_SPEED
|
|
127
|
+
* pictures a second) it runs on at that speed to the whole picture past where the scroll will stop, and stands there: it
|
|
128
|
+
* never crawls through a dissolve, never stops between two pictures, and never steps back unless the scroll does.
|
|
129
|
+
*/
|
|
130
|
+
function stepFilm(motion, wanted, seconds, last) {
|
|
131
|
+
const sameFrame = seconds < FILM_SAME_FRAME_SECONDS;
|
|
132
|
+
const step = Math.min(FILM_MAX_STEP_SECONDS, seconds);
|
|
133
|
+
const moved = wanted - motion.wanted;
|
|
134
|
+
const speed = sameFrame ? motion.speed : motion.speed + (moved / step - motion.speed) * (1 - Math.exp(-FILM_SPEED_RATE * step));
|
|
135
|
+
const direction = Math.abs(moved) > FILM_EPSILON ? (moved > 0 ? 1 : -1) : motion.direction;
|
|
136
|
+
// Moving: with the scroll, but never back over a picture a landing already reached unless the scroll turned.
|
|
137
|
+
if (Math.abs(speed) >= FILM_LANDING_SPEED) {
|
|
138
|
+
const position = direction > 0 ? Math.max(wanted, Math.min(motion.position, Math.ceil(wanted))) : Math.min(wanted, Math.max(motion.position, Math.floor(wanted)));
|
|
139
|
+
return { position, wanted, speed, direction };
|
|
140
|
+
}
|
|
141
|
+
if (sameFrame)
|
|
142
|
+
return { ...motion, wanted, direction };
|
|
143
|
+
const clamp = (value) => Math.min(last, Math.max(0, value));
|
|
144
|
+
const stop = wanted + speed / FILM_LANDING_DECAY;
|
|
145
|
+
const reached = direction > 0 ? Math.ceil(motion.position - FILM_EPSILON) : Math.floor(motion.position + FILM_EPSILON);
|
|
146
|
+
const goal = clamp(direction > 0 ? Math.max(Math.ceil(stop - FILM_EPSILON), reached) : Math.min(Math.floor(stop + FILM_EPSILON), reached));
|
|
147
|
+
const behind = (wanted - motion.position) * direction;
|
|
148
|
+
const reach = Math.max(FILM_LANDING_SPEED * step, behind > 0 ? behind * (1 - Math.exp(-FILM_CATCH_RATE * step)) : 0);
|
|
149
|
+
const position = direction > 0 ? Math.min(goal, motion.position + reach) : Math.max(goal, motion.position - reach);
|
|
150
|
+
return { position, wanted, speed, direction };
|
|
151
|
+
}
|
|
152
|
+
/** Whether a film has landed: still, on the whole picture at or just past where the scroll stands. */
|
|
153
|
+
function filmLanded(motion) {
|
|
154
|
+
const past = (motion.position - motion.wanted) * motion.direction;
|
|
155
|
+
return Math.abs(motion.speed) < 0.01 && Number.isInteger(motion.position) && past > -FILM_EPSILON && past < 1;
|
|
156
|
+
}
|
|
108
157
|
/** The picture at `played` % of a set of `count`. */
|
|
109
158
|
function frameIndexAt(played, count) {
|
|
110
159
|
if (count <= 1)
|
|
@@ -199,12 +248,12 @@ class FramePlayer {
|
|
|
199
248
|
this.el = el;
|
|
200
249
|
this.sequence = sequence;
|
|
201
250
|
this.played = null;
|
|
202
|
-
this.
|
|
203
|
-
this.
|
|
251
|
+
this.motion = null;
|
|
252
|
+
this.steppedAt = 0;
|
|
253
|
+
this.landingFrame = null;
|
|
204
254
|
this.observer = null;
|
|
205
255
|
this.canvas = null;
|
|
206
256
|
this.context = null;
|
|
207
|
-
this.position = 0;
|
|
208
257
|
this.drawn = null;
|
|
209
258
|
this.nextToLoad = 0;
|
|
210
259
|
this.loading = 0;
|
|
@@ -235,34 +284,27 @@ class FramePlayer {
|
|
|
235
284
|
if (played === this.played)
|
|
236
285
|
return;
|
|
237
286
|
this.played = played;
|
|
238
|
-
this.
|
|
239
|
-
|
|
287
|
+
this.step();
|
|
288
|
+
}
|
|
289
|
+
/** One step toward the moment asked for, drawn now; a landing goes on by itself until it stands on a whole picture. */
|
|
290
|
+
step() {
|
|
291
|
+
if (this.destroyed || this.played === null)
|
|
292
|
+
return;
|
|
293
|
+
const wanted = framePosition(this.played, this.info.count);
|
|
294
|
+
const now = this.host.performance?.now() ?? Date.now();
|
|
295
|
+
this.motion = this.motion ? stepFilm(this.motion, wanted, (now - this.steppedAt) / 1000, this.info.count - 1) : filmAt(wanted);
|
|
296
|
+
this.steppedAt = now;
|
|
240
297
|
this.draw();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const target = Math.round(this.position);
|
|
247
|
-
const step = () => {
|
|
248
|
-
this.restFrame = null;
|
|
249
|
-
if (this.destroyed)
|
|
250
|
-
return;
|
|
251
|
-
const left = target - this.position;
|
|
252
|
-
this.position = Math.abs(left) < 1 / FRAME_BLEND_STEPS ? target : this.position + left * REST_RATE;
|
|
253
|
-
this.draw();
|
|
254
|
-
if (this.position !== target)
|
|
255
|
-
this.restFrame = this.host.requestAnimationFrame(step);
|
|
256
|
-
};
|
|
257
|
-
step();
|
|
298
|
+
if (!filmLanded(this.motion) && this.landingFrame === null)
|
|
299
|
+
this.landingFrame = this.host.requestAnimationFrame(() => {
|
|
300
|
+
this.landingFrame = null;
|
|
301
|
+
this.step();
|
|
302
|
+
});
|
|
258
303
|
}
|
|
259
|
-
|
|
260
|
-
if (this.
|
|
261
|
-
this.host.
|
|
262
|
-
|
|
263
|
-
this.host.cancelAnimationFrame?.(this.restFrame);
|
|
264
|
-
this.restTimer = null;
|
|
265
|
-
this.restFrame = null;
|
|
304
|
+
stopLanding() {
|
|
305
|
+
if (this.landingFrame !== null)
|
|
306
|
+
this.host.cancelAnimationFrame?.(this.landingFrame);
|
|
307
|
+
this.landingFrame = null;
|
|
266
308
|
}
|
|
267
309
|
/** Whether a re-render took the canvas away with the markup it sat in. */
|
|
268
310
|
get detached() {
|
|
@@ -270,7 +312,7 @@ class FramePlayer {
|
|
|
270
312
|
}
|
|
271
313
|
destroy() {
|
|
272
314
|
this.destroyed = true;
|
|
273
|
-
this.
|
|
315
|
+
this.stopLanding();
|
|
274
316
|
this.el.removeAttribute(FRAMES_SHOWING_ATTRIBUTE);
|
|
275
317
|
this.observer?.disconnect();
|
|
276
318
|
this.observer = null;
|
|
@@ -330,7 +372,9 @@ class FramePlayer {
|
|
|
330
372
|
image.src = frameUrl(this.sequence, this.set, index);
|
|
331
373
|
}
|
|
332
374
|
draw() {
|
|
333
|
-
|
|
375
|
+
if (!this.motion)
|
|
376
|
+
return;
|
|
377
|
+
const blend = frameBlendAt(this.images, this.motion.position);
|
|
334
378
|
const last = this.drawn;
|
|
335
379
|
if (!blend || (last && last.from === blend.from && last.to === blend.to && last.mix === blend.mix))
|
|
336
380
|
return;
|
|
@@ -988,5 +1032,5 @@ function videoRuntimeOf(el) {
|
|
|
988
1032
|
* Generated bundle index. Do not edit.
|
|
989
1033
|
*/
|
|
990
1034
|
|
|
991
|
-
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameCount, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
|
|
1035
|
+
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FILM_LANDING_SPEED, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, filmAt, filmLanded, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameCount, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, stepFilm, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
|
|
992
1036
|
//# sourceMappingURL=vectoriox-iox-ui-media-core.mjs.map
|