@cntrl-site/sdk 1.25.11 → 1.25.12
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/lib/ScrollPlaybackVideoManager/ScrollPlaybackVideoManager.d.ts +13 -0
- package/lib/ScrollPlaybackVideoManager/ScrollPlaybackVideoManager.js +74 -7
- package/lib/index.d.ts +1 -1
- package/lib/schemas/article/Item.schema.js +9 -0
- package/lib/types/article/Item.d.ts +9 -0
- package/package.json +1 -1
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
interface ScrollVideoOptions {
|
|
2
2
|
src: string;
|
|
3
3
|
videoContainer: HTMLElement | string;
|
|
4
|
+
frameData?: {
|
|
5
|
+
batchId: string;
|
|
6
|
+
frameCount: number;
|
|
7
|
+
frameRate: number;
|
|
8
|
+
frameFormat: string;
|
|
9
|
+
};
|
|
10
|
+
frameBaseUrl?: string;
|
|
4
11
|
}
|
|
5
12
|
export declare class ScrollPlaybackVideoManager {
|
|
6
13
|
private container?;
|
|
@@ -18,7 +25,13 @@ export declare class ScrollPlaybackVideoManager {
|
|
|
18
25
|
private transitionSpeed;
|
|
19
26
|
private useWebCodecs;
|
|
20
27
|
private resizeObserver;
|
|
28
|
+
private serverFrameData?;
|
|
29
|
+
private serverFrameBaseUrl?;
|
|
30
|
+
private serverFrameImages;
|
|
31
|
+
private currentServerFrameIdx;
|
|
21
32
|
constructor(options: ScrollVideoOptions);
|
|
33
|
+
private loadServerFrames;
|
|
34
|
+
private paintServerFrame;
|
|
22
35
|
private setCoverStyle;
|
|
23
36
|
private resize;
|
|
24
37
|
private decodeVideo;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
@@ -19,16 +28,21 @@ class ScrollPlaybackVideoManager {
|
|
|
19
28
|
this.frameThreshold = 0.1;
|
|
20
29
|
this.transitionSpeed = 10;
|
|
21
30
|
this.useWebCodecs = true;
|
|
31
|
+
this.serverFrameImages = [];
|
|
32
|
+
this.currentServerFrameIdx = 0;
|
|
22
33
|
this.resize = () => {
|
|
23
34
|
if (this.debug)
|
|
24
35
|
console.info('ScrollVideo resizing...');
|
|
25
|
-
if (this.
|
|
36
|
+
if (this.serverFrameData && this.serverFrameImages.length > 0) {
|
|
37
|
+
this.paintServerFrame(this.currentServerFrameIdx);
|
|
38
|
+
}
|
|
39
|
+
else if (this.canvas) {
|
|
26
40
|
this.setCoverStyle(this.canvas);
|
|
41
|
+
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
|
|
27
42
|
}
|
|
28
43
|
else if (this.video) {
|
|
29
44
|
this.setCoverStyle(this.video);
|
|
30
45
|
}
|
|
31
|
-
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
|
|
32
46
|
};
|
|
33
47
|
this.decodeVideo = () => {
|
|
34
48
|
if (!this.video)
|
|
@@ -63,7 +77,7 @@ class ScrollPlaybackVideoManager {
|
|
|
63
77
|
this.resizeObserver = new ResizeObserver(() => {
|
|
64
78
|
this.resize();
|
|
65
79
|
});
|
|
66
|
-
const { src, videoContainer } = options;
|
|
80
|
+
const { src, videoContainer, frameData, frameBaseUrl } = options;
|
|
67
81
|
if (typeof document !== 'object') {
|
|
68
82
|
console.error('ScrollVideo must be initiated in a DOM context');
|
|
69
83
|
return;
|
|
@@ -78,6 +92,12 @@ class ScrollPlaybackVideoManager {
|
|
|
78
92
|
}
|
|
79
93
|
this.container = typeof videoContainer === 'string' ? document.getElementById(videoContainer) : videoContainer;
|
|
80
94
|
this.resizeObserver.observe(this.container);
|
|
95
|
+
if (frameData && frameBaseUrl) {
|
|
96
|
+
this.serverFrameData = frameData;
|
|
97
|
+
this.serverFrameBaseUrl = frameBaseUrl;
|
|
98
|
+
this.loadServerFrames();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
81
101
|
this.video = document.createElement('video');
|
|
82
102
|
this.video.src = src;
|
|
83
103
|
this.video.preload = 'auto';
|
|
@@ -90,16 +110,58 @@ class ScrollPlaybackVideoManager {
|
|
|
90
110
|
const ua = new ua_parser_js_1.default();
|
|
91
111
|
const browserEngine = ua.getEngine();
|
|
92
112
|
this.isSafari = browserEngine.name === 'WebKit';
|
|
93
|
-
const deviceType = ua.getDevice().type;
|
|
94
|
-
if (deviceType === 'mobile' || deviceType === 'tablet') {
|
|
95
|
-
this.useWebCodecs = false;
|
|
96
|
-
}
|
|
97
113
|
if (this.debug && this.isSafari)
|
|
98
114
|
console.info('Safari browser detected');
|
|
99
115
|
this.video.addEventListener('loadedmetadata', () => this.setTargetTimePercent(0, true), { once: true });
|
|
100
116
|
this.video.addEventListener('progress', this.resize);
|
|
101
117
|
this.decodeVideo();
|
|
102
118
|
}
|
|
119
|
+
loadServerFrames() {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
+
if (!this.serverFrameData || !this.serverFrameBaseUrl || !this.container)
|
|
122
|
+
return;
|
|
123
|
+
const { batchId, frameCount, frameRate } = this.serverFrameData;
|
|
124
|
+
const loadBatch = (start, end) => __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
const promises = [];
|
|
126
|
+
for (let i = start; i <= end; i++) {
|
|
127
|
+
const idx = String(i).padStart(4, '0');
|
|
128
|
+
const url = `${this.serverFrameBaseUrl}/${batchId}_${idx}.${this.serverFrameData.frameFormat}`;
|
|
129
|
+
const img = new Image();
|
|
130
|
+
this.serverFrameImages[i - 1] = img;
|
|
131
|
+
promises.push(new Promise((resolve) => {
|
|
132
|
+
img.onload = () => resolve();
|
|
133
|
+
img.onerror = () => resolve();
|
|
134
|
+
img.src = url;
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
yield Promise.all(promises);
|
|
138
|
+
});
|
|
139
|
+
const initialBatch = Math.min(10, frameCount);
|
|
140
|
+
yield loadBatch(1, initialBatch);
|
|
141
|
+
this.frameRate = frameRate;
|
|
142
|
+
this.canvas = document.createElement('canvas');
|
|
143
|
+
this.context = this.canvas.getContext('2d');
|
|
144
|
+
this.container.appendChild(this.canvas);
|
|
145
|
+
this.paintServerFrame(0);
|
|
146
|
+
if (initialBatch < frameCount) {
|
|
147
|
+
loadBatch(initialBatch + 1, frameCount);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
paintServerFrame(frameIdx) {
|
|
152
|
+
if (!this.canvas || !this.context || !this.container)
|
|
153
|
+
return;
|
|
154
|
+
const clampedIdx = Math.max(0, Math.min(frameIdx, this.serverFrameImages.length - 1));
|
|
155
|
+
const img = this.serverFrameImages[clampedIdx];
|
|
156
|
+
if (!img || !img.naturalWidth)
|
|
157
|
+
return;
|
|
158
|
+
this.currentServerFrameIdx = clampedIdx;
|
|
159
|
+
this.canvas.width = img.naturalWidth;
|
|
160
|
+
this.canvas.height = img.naturalHeight;
|
|
161
|
+
const { width, height } = this.container.getBoundingClientRect();
|
|
162
|
+
this.resetCanvasDimensions(width, height, img.naturalWidth, img.naturalHeight);
|
|
163
|
+
this.context.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
|
|
164
|
+
}
|
|
103
165
|
setCoverStyle(el) {
|
|
104
166
|
if (el && this.container) {
|
|
105
167
|
el.style.position = 'absolute';
|
|
@@ -200,6 +262,11 @@ class ScrollPlaybackVideoManager {
|
|
|
200
262
|
}
|
|
201
263
|
}
|
|
202
264
|
setTargetTimePercent(setPercentage, jump = true) {
|
|
265
|
+
if (this.serverFrameData && this.serverFrameImages.length > 0) {
|
|
266
|
+
const frameIdx = Math.round(Math.max(0, Math.min(setPercentage, 1)) * (this.serverFrameImages.length - 1));
|
|
267
|
+
this.paintServerFrame(frameIdx);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
203
270
|
if (!this.video)
|
|
204
271
|
return;
|
|
205
272
|
this.targetTime = Math.max(Math.min(setPercentage, 1), 0)
|
package/lib/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { AreaAnchor, AnchorSide, DimensionMode, PositionType, DimensionsType } f
|
|
|
10
10
|
export { KeyframeType } from './types/keyframe/Keyframe';
|
|
11
11
|
export type { Article } from './types/article/Article';
|
|
12
12
|
export type { Section, SectionHeight } from './types/article/Section';
|
|
13
|
-
export type { Item, ImageItem, ItemAny, CustomItem, ItemCommonParamsMap, ItemLayoutParamsMap, RectangleItem, StickyParams, VideoItem, RichTextItem, Link, VimeoEmbedItem, YoutubeEmbedItem, GroupItem, CodeEmbedItem, CompoundItem, ComponentItem, FillLayer } from './types/article/Item';
|
|
13
|
+
export type { Item, ImageItem, ItemAny, CustomItem, ItemCommonParamsMap, ItemLayoutParamsMap, RectangleItem, StickyParams, VideoItem, RichTextItem, Link, VimeoEmbedItem, YoutubeEmbedItem, GroupItem, CodeEmbedItem, CompoundItem, ComponentItem, FillLayer, ScrollPlaybackFrameData } from './types/article/Item';
|
|
14
14
|
export type { RichTextBlock, RichTextEntity, RichTextStyle } from './types/article/RichText';
|
|
15
15
|
export type { ItemArea } from './types/article/ItemArea';
|
|
16
16
|
export type { ItemState, ItemStateParams, StateParams, ItemStatesMap } from './types/article/ItemState';
|
|
@@ -52,10 +52,19 @@ const ImageItemSchema = ItemBase_schema_1.ItemBaseSchema.extend({
|
|
|
52
52
|
})),
|
|
53
53
|
state: zod_1.z.record(ItemState_schema_1.MediaStateParamsSchema)
|
|
54
54
|
});
|
|
55
|
+
const ScrollPlaybackFrameDataSchema = zod_1.z.object({
|
|
56
|
+
status: zod_1.z.enum(['processing', 'ready', 'error']),
|
|
57
|
+
batchId: zod_1.z.string().optional(),
|
|
58
|
+
frameCount: zod_1.z.number().optional(),
|
|
59
|
+
frameRate: zod_1.z.number().optional(),
|
|
60
|
+
framesUrl: zod_1.z.string().optional(),
|
|
61
|
+
frameFormat: zod_1.z.string().optional()
|
|
62
|
+
});
|
|
55
63
|
const VideoItemSchema = ItemBase_schema_1.ItemBaseSchema.extend({
|
|
56
64
|
type: zod_1.z.literal(ArticleItemType_1.ArticleItemType.Video),
|
|
57
65
|
commonParams: zod_1.z.object({
|
|
58
66
|
coverUrl: zod_1.z.string().nullable(),
|
|
67
|
+
scrollPlaybackFrameData: ScrollPlaybackFrameDataSchema.nullable().optional(),
|
|
59
68
|
pointerEvents
|
|
60
69
|
}).merge(FXParams),
|
|
61
70
|
sticky: zod_1.z.record(zod_1.z.object({
|
|
@@ -55,6 +55,7 @@ interface MediaCommonParams extends CommonParamsBase {
|
|
|
55
55
|
}
|
|
56
56
|
interface VideoCommonParams extends MediaCommonParams {
|
|
57
57
|
coverUrl: string | null;
|
|
58
|
+
scrollPlaybackFrameData?: ScrollPlaybackFrameData | null;
|
|
58
59
|
}
|
|
59
60
|
interface ImageCommonParams extends MediaCommonParams {
|
|
60
61
|
}
|
|
@@ -226,6 +227,14 @@ export interface ScrollPlaybackParams {
|
|
|
226
227
|
from: number;
|
|
227
228
|
to: number;
|
|
228
229
|
}
|
|
230
|
+
export interface ScrollPlaybackFrameData {
|
|
231
|
+
status: 'processing' | 'ready' | 'error';
|
|
232
|
+
batchId?: string;
|
|
233
|
+
frameCount?: number;
|
|
234
|
+
frameRate?: number;
|
|
235
|
+
framesUrl?: string;
|
|
236
|
+
frameFormat?: string;
|
|
237
|
+
}
|
|
229
238
|
export interface StickyParams {
|
|
230
239
|
from: number;
|
|
231
240
|
to?: number;
|