cloudinary-video-player 3.13.1 → 3.13.2-edge.1
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/dist/134.min.js +4 -4
- package/dist/309.min.js +3 -3
- package/dist/350.min.js +6 -0
- package/dist/689.min.js +6 -0
- package/dist/adaptive-streaming.js +2 -2
- package/dist/adaptive-streaming.min.js +2 -2
- package/dist/chapters.js +2 -2
- package/dist/chapters.min.js +2 -2
- package/dist/cld-player-core.js +2 -2
- package/dist/cld-player-core.min.js +2 -2
- package/dist/cld-poster-url.js +57 -0
- package/dist/cld-poster-url.min.js +6 -0
- package/dist/cld-video-player-lazy.js +10 -66
- package/dist/cld-video-player-lazy.min.js +3 -3
- package/dist/cld-video-player.css +6 -2
- package/dist/cld-video-player.js +35 -45
- package/dist/cld-video-player.light.js +35 -45
- package/dist/cld-video-player.light.min.js +4 -4
- package/dist/cld-video-player.min.css +3 -3
- package/dist/cld-video-player.min.js +4 -4
- package/dist/colors.js +2 -2
- package/dist/colors.min.js +2 -2
- package/dist/dash.js +2 -2
- package/dist/dash.min.js +2 -2
- package/dist/debug.js +3 -3
- package/dist/debug.min.js +3 -3
- package/dist/ima.js +2 -2
- package/dist/ima.min.js +2 -2
- package/dist/interaction-areas.js +2 -2
- package/dist/interaction-areas.min.js +2 -2
- package/dist/node_modules_lodash_throttle_js.js +2 -2
- package/dist/playlist.js +2 -2
- package/dist/playlist.min.js +2 -2
- package/dist/{plugins_cloudinary_url-helpers_js-utils_object_js-utils_querystring_js-utils_utf8Base64_js-vi-555007.js → plugins_cloudinary_url-helpers_js-utils_cloudinary-config-from-options_js-utils_querystring_j-adfe37.js} +48 -4
- package/dist/recommendations-overlay.js +2 -2
- package/dist/recommendations-overlay.min.js +2 -2
- package/dist/schema.json +16 -0
- package/dist/share.js +2 -2
- package/dist/share.min.js +2 -2
- package/dist/shoppable.js +2 -2
- package/dist/shoppable.min.js +2 -2
- package/dist/utils_fetch-config_js.js +3 -3
- package/dist/utils_schedule_js.js +27 -0
- package/dist/video-player_js.js +11 -11
- package/dist/visual-search.js +2 -2
- package/dist/visual-search.min.js +2 -2
- package/lib/all.js +3 -2
- package/lib/chapters.js +2 -2
- package/lib/cld-video-player.min.css +3 -3
- package/lib/{object.js → cloudinary-config-from-options.js} +10 -2
- package/lib/cloudinary-url-prefix.js +91 -0
- package/lib/colors.js +2 -2
- package/lib/config/configSchema.json +16 -0
- package/lib/debug.js +1 -0
- package/lib/fetch-config.js +4 -11
- package/lib/index.js +3 -2
- package/lib/interaction-areas.service.js +2 -2
- package/lib/lazy.js +0 -1
- package/lib/player-api.js +171 -413
- package/lib/player.js +2 -1
- package/lib/playlist.js +2 -2
- package/lib/poster-url.js +46 -0
- package/lib/recommendations-overlay.js +2 -2
- package/lib/schedule.js +153 -0
- package/lib/share.js +2 -2
- package/lib/shoppable-widget.js +2 -2
- package/lib/toNumber.js +1 -1
- package/lib/video-player.const.js +1 -1
- package/lib/video-player.js +25 -11
- package/lib/videoPlayer.js +2 -1
- package/package.json +1 -1
- package/types/cld-video-player.d.ts +6 -0
- package/dist/19.min.js +0 -6
package/lib/player-api.js
CHANGED
|
@@ -1,390 +1,13 @@
|
|
|
1
|
-
import { c as commonjsGlobal, g as getDefaultExportFromCjs } from './_commonjsHelpers.js';
|
|
2
|
-
|
|
3
|
-
const LEGACY_CONDITIONAL_OPERATORS = {
|
|
4
|
-
"=": 'eq',
|
|
5
|
-
"!=": 'ne',
|
|
6
|
-
"<": 'lt',
|
|
7
|
-
">": 'gt',
|
|
8
|
-
"<=": 'lte',
|
|
9
|
-
">=": 'gte',
|
|
10
|
-
"&&": 'and',
|
|
11
|
-
"||": 'or',
|
|
12
|
-
"*": "mul",
|
|
13
|
-
"/": "div",
|
|
14
|
-
"+": "add",
|
|
15
|
-
"-": "sub",
|
|
16
|
-
"^": "pow"
|
|
17
|
-
};
|
|
18
|
-
const OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net";
|
|
19
|
-
const AKAMAI_SHARED_CDN = "res.cloudinary.com";
|
|
20
|
-
const SHARED_CDN = AKAMAI_SHARED_CDN;
|
|
21
|
-
const LEGACY_PREDEFINED_VARS = {
|
|
22
|
-
"aspect_ratio": "ar",
|
|
23
|
-
"aspectRatio": "ar",
|
|
24
|
-
"current_page": "cp",
|
|
25
|
-
"currentPage": "cp",
|
|
26
|
-
"duration": "du",
|
|
27
|
-
"face_count": "fc",
|
|
28
|
-
"faceCount": "fc",
|
|
29
|
-
"height": "h",
|
|
30
|
-
"initial_aspect_ratio": "iar",
|
|
31
|
-
"initial_height": "ih",
|
|
32
|
-
"initial_width": "iw",
|
|
33
|
-
"initialAspectRatio": "iar",
|
|
34
|
-
"initialHeight": "ih",
|
|
35
|
-
"initialWidth": "iw",
|
|
36
|
-
"initial_duration": "idu",
|
|
37
|
-
"initialDuration": "idu",
|
|
38
|
-
"page_count": "pc",
|
|
39
|
-
"page_x": "px",
|
|
40
|
-
"page_y": "py",
|
|
41
|
-
"pageCount": "pc",
|
|
42
|
-
"pageX": "px",
|
|
43
|
-
"pageY": "py",
|
|
44
|
-
"tags": "tags",
|
|
45
|
-
"width": "w"
|
|
46
|
-
};
|
|
47
|
-
const NUMBER_PATTERN = "([0-9]*)\\.([0-9]+)|([0-9]+)";
|
|
48
|
-
const OFFSET_ANY_PATTERN = `(${NUMBER_PATTERN})([%pP])?`;
|
|
49
|
-
const RANGE_VALUE_RE = RegExp(`^${OFFSET_ANY_PATTERN}$`);
|
|
50
|
-
const OFFSET_ANY_PATTERN_RE = RegExp(`(${OFFSET_ANY_PATTERN})\\.\\.(${OFFSET_ANY_PATTERN})`);
|
|
51
|
-
const LAYER_KEYWORD_PARAMS = {
|
|
52
|
-
font_weight: "normal",
|
|
53
|
-
font_style: "normal",
|
|
54
|
-
text_decoration: "none",
|
|
55
|
-
text_align: '',
|
|
56
|
-
stroke: "none"
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function unsigned_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution) {
|
|
60
|
-
let prefix;
|
|
61
|
-
if (cloud_name.indexOf("/") === 0) {
|
|
62
|
-
return '/res' + cloud_name;
|
|
63
|
-
}
|
|
64
|
-
let shared_domain = !private_cdn;
|
|
65
|
-
if (secure) {
|
|
66
|
-
if ((secure_distribution == null) || secure_distribution === OLD_AKAMAI_SHARED_CDN) {
|
|
67
|
-
secure_distribution = private_cdn ? cloud_name + "-res.cloudinary.com" : SHARED_CDN;
|
|
68
|
-
}
|
|
69
|
-
if (shared_domain == null) {
|
|
70
|
-
shared_domain = secure_distribution === SHARED_CDN;
|
|
71
|
-
}
|
|
72
|
-
prefix = 'https://' + secure_distribution;
|
|
73
|
-
}
|
|
74
|
-
else if (cname) {
|
|
75
|
-
// let subdomain = cdn_subdomain ? 'a' + ((crc32(source) % 5) + 1) + '.' : '';
|
|
76
|
-
prefix = 'http://' + cname;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
let cdn_part = private_cdn ? cloud_name + '-' : '';
|
|
80
|
-
let host = [cdn_part, 'res', '.cloudinary.com'].join('');
|
|
81
|
-
prefix = 'http://' + host;
|
|
82
|
-
}
|
|
83
|
-
if (shared_domain) {
|
|
84
|
-
prefix += '/' + cloud_name;
|
|
85
|
-
}
|
|
86
|
-
return prefix;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const getCloudinaryUrlPrefix = cloudinaryConfig => {
|
|
90
|
-
return unsigned_url_prefix(null, cloudinaryConfig.cloud_name, cloudinaryConfig.private_cdn, cloudinaryConfig.cdn_subdomain, cloudinaryConfig.secure_cdn_subdomain, cloudinaryConfig.cname, cloudinaryConfig.secure ?? true, cloudinaryConfig.secure_distribution);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
var css_escape$1 = {exports: {}};
|
|
94
|
-
|
|
95
|
-
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
|
|
96
|
-
var css_escape = css_escape$1.exports;
|
|
97
|
-
|
|
98
|
-
var hasRequiredCss_escape;
|
|
99
|
-
|
|
100
|
-
function requireCss_escape () {
|
|
101
|
-
if (hasRequiredCss_escape) return css_escape$1.exports;
|
|
102
|
-
hasRequiredCss_escape = 1;
|
|
103
|
-
(function (module, exports$1) {
|
|
104
|
-
(function(root, factory) {
|
|
105
|
-
// https://github.com/umdjs/umd/blob/master/returnExports.js
|
|
106
|
-
{
|
|
107
|
-
// For Node.js.
|
|
108
|
-
module.exports = factory(root);
|
|
109
|
-
}
|
|
110
|
-
}(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : css_escape, function(root) {
|
|
111
|
-
|
|
112
|
-
if (root.CSS && root.CSS.escape) {
|
|
113
|
-
return root.CSS.escape;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// https://drafts.csswg.org/cssom/#serialize-an-identifier
|
|
117
|
-
var cssEscape = function(value) {
|
|
118
|
-
if (arguments.length == 0) {
|
|
119
|
-
throw new TypeError('`CSS.escape` requires an argument.');
|
|
120
|
-
}
|
|
121
|
-
var string = String(value);
|
|
122
|
-
var length = string.length;
|
|
123
|
-
var index = -1;
|
|
124
|
-
var codeUnit;
|
|
125
|
-
var result = '';
|
|
126
|
-
var firstCodeUnit = string.charCodeAt(0);
|
|
127
|
-
while (++index < length) {
|
|
128
|
-
codeUnit = string.charCodeAt(index);
|
|
129
|
-
// Note: there’s no need to special-case astral symbols, surrogate
|
|
130
|
-
// pairs, or lone surrogates.
|
|
131
|
-
|
|
132
|
-
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
|
|
133
|
-
// (U+FFFD).
|
|
134
|
-
if (codeUnit == 0x0000) {
|
|
135
|
-
result += '\uFFFD';
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
|
|
141
|
-
// U+007F, […]
|
|
142
|
-
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
|
|
143
|
-
// If the character is the first character and is in the range [0-9]
|
|
144
|
-
// (U+0030 to U+0039), […]
|
|
145
|
-
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
|
|
146
|
-
// If the character is the second character and is in the range [0-9]
|
|
147
|
-
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
|
|
148
|
-
(
|
|
149
|
-
index == 1 &&
|
|
150
|
-
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
|
|
151
|
-
firstCodeUnit == 0x002D
|
|
152
|
-
)
|
|
153
|
-
) {
|
|
154
|
-
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
|
|
155
|
-
result += '\\' + codeUnit.toString(16) + ' ';
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
// If the character is the first character and is a `-` (U+002D), and
|
|
161
|
-
// there is no second character, […]
|
|
162
|
-
index == 0 &&
|
|
163
|
-
length == 1 &&
|
|
164
|
-
codeUnit == 0x002D
|
|
165
|
-
) {
|
|
166
|
-
result += '\\' + string.charAt(index);
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// If the character is not handled by one of the above rules and is
|
|
171
|
-
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
|
|
172
|
-
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
|
|
173
|
-
// U+005A), or [a-z] (U+0061 to U+007A), […]
|
|
174
|
-
if (
|
|
175
|
-
codeUnit >= 0x0080 ||
|
|
176
|
-
codeUnit == 0x002D ||
|
|
177
|
-
codeUnit == 0x005F ||
|
|
178
|
-
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
|
|
179
|
-
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
|
|
180
|
-
codeUnit >= 0x0061 && codeUnit <= 0x007A
|
|
181
|
-
) {
|
|
182
|
-
// the character itself
|
|
183
|
-
result += string.charAt(index);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Otherwise, the escaped character.
|
|
188
|
-
// https://drafts.csswg.org/cssom/#escape-a-character
|
|
189
|
-
result += '\\' + string.charAt(index);
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
if (!root.CSS) {
|
|
196
|
-
root.CSS = {};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
root.CSS.escape = cssEscape;
|
|
200
|
-
return cssEscape;
|
|
201
|
-
|
|
202
|
-
}));
|
|
203
|
-
} (css_escape$1));
|
|
204
|
-
return css_escape$1.exports;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
var css_escapeExports = requireCss_escape();
|
|
208
|
-
var cssEscape = /*@__PURE__*/getDefaultExportFromCjs(css_escapeExports);
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Minimal Cloudinary poster URL builder for video first frame.
|
|
212
|
-
* Used by schedule bootstrap when outside schedule (no full player loaded).
|
|
213
|
-
*/
|
|
214
|
-
const POSTER_TRANSFORMATION = 'so_0,f_auto,q_auto';
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Build Cloudinary video poster (first frame) URL.
|
|
218
|
-
* @param {string} cloudName - Cloudinary cloud name
|
|
219
|
-
* @param {string} publicId - Video public ID
|
|
220
|
-
* @param {object} [cloudinaryConfig] - Optional: secure, private_cdn, cdn_subdomain, cname, secure_distribution
|
|
221
|
-
* @returns {string} Poster image URL
|
|
222
|
-
*/
|
|
223
|
-
const buildPosterUrl = function (cloudName, publicId) {
|
|
224
|
-
let cloudinaryConfig = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
225
|
-
const config = {
|
|
226
|
-
cloud_name: cloudName || cloudinaryConfig.cloud_name,
|
|
227
|
-
...cloudinaryConfig,
|
|
228
|
-
secure: cloudinaryConfig.secure ?? true
|
|
229
|
-
};
|
|
230
|
-
const prefix = getCloudinaryUrlPrefix(config);
|
|
231
|
-
return `${prefix}/video/upload/${POSTER_TRANSFORMATION}/${publicId}`;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Schedule utilities: weekly time-range parsing and bootstrap (poster rendering).
|
|
236
|
-
* Uses browser local time. No videojs dependency for the bootstrap path.
|
|
237
|
-
*/
|
|
238
|
-
const INTERNAL_ANALYTICS_URL = 'https://analytics-api-s.cloudinary.com';
|
|
239
|
-
const sendScheduleImageAnalytics = options => {
|
|
240
|
-
const allowReport = options?.sourceOptions?.allowUsageReport ?? options?.allowUsageReport;
|
|
241
|
-
if (allowReport === false) return;
|
|
242
|
-
try {
|
|
243
|
-
const params = new URLSearchParams({
|
|
244
|
-
scheduleImageRendered: 'true',
|
|
245
|
-
cloudName: options?.cloudName || options?.cloudinaryConfig?.cloud_name || ''
|
|
246
|
-
}).toString();
|
|
247
|
-
fetch(`${INTERNAL_ANALYTICS_URL}/video_player_source?${params}`);
|
|
248
|
-
} catch {
|
|
249
|
-
// noop
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
const getCloudNameFromOptions = options => options?.cloudName || options?.cloud_name || options?.cloudinaryConfig?.cloud_name;
|
|
253
|
-
const getPublicIdFromOptions = options => options?.publicId || options?.sourceOptions?.publicId;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Returns true when schedule.weekly is configured and current time is outside the schedule.
|
|
257
|
-
* @param {object} options - player options
|
|
258
|
-
* @returns {boolean}
|
|
259
|
-
*/
|
|
260
|
-
const shouldUseScheduleBootstrap = options => {
|
|
261
|
-
const schedule = options?.schedule;
|
|
262
|
-
const weekly = schedule?.weekly;
|
|
263
|
-
return Array.isArray(weekly) && weekly.length > 0 && !isWithinSchedule(schedule, new Date());
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Bootstrap path when outside schedule: render poster, return stub with loadPlayer().
|
|
268
|
-
* @param {string|HTMLElement} elem - Element id or video element
|
|
269
|
-
* @param {object} options - player options
|
|
270
|
-
* @returns {object} Stub with source() and loadPlayer()
|
|
271
|
-
*/
|
|
272
|
-
const scheduleBootstrap = (elem, options) => {
|
|
273
|
-
const videoElement = getElementForSchedule(elem);
|
|
274
|
-
const cloudName = getCloudNameFromOptions(options);
|
|
275
|
-
const publicId = getPublicIdFromOptions(options);
|
|
276
|
-
if (!cloudName || !publicId) {
|
|
277
|
-
throw new Error('schedule.weekly requires cloudName and publicId when outside schedule');
|
|
278
|
-
}
|
|
279
|
-
const cloudinaryConfig = options?.cloudinaryConfig || {
|
|
280
|
-
cloud_name: cloudName
|
|
281
|
-
};
|
|
282
|
-
const posterUrl = buildPosterUrl(cloudName, publicId, cloudinaryConfig);
|
|
283
|
-
const fluid = options?.fluid !== false;
|
|
284
|
-
const {
|
|
285
|
-
container,
|
|
286
|
-
videoElement: vEl
|
|
287
|
-
} = renderScheduleImage(videoElement, posterUrl, {
|
|
288
|
-
fluid,
|
|
289
|
-
width: options?.width,
|
|
290
|
-
height: options?.height,
|
|
291
|
-
cropMode: options?.sourceOptions?.cropMode
|
|
292
|
-
});
|
|
293
|
-
sendScheduleImageAnalytics(options);
|
|
294
|
-
const stub = {
|
|
295
|
-
source: () => stub,
|
|
296
|
-
loadPlayer: () => {
|
|
297
|
-
if (container && container.parentNode) {
|
|
298
|
-
container.parentNode.removeChild(container);
|
|
299
|
-
}
|
|
300
|
-
vEl.style.display = '';
|
|
301
|
-
return import('./video-player.js').then(function (n) { return n.v; }).then(m => m.createVideoPlayer(vEl, options));
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
return stub;
|
|
305
|
-
};
|
|
306
|
-
const DAY_MAP = {
|
|
307
|
-
sunday: 0,
|
|
308
|
-
sun: 0,
|
|
309
|
-
monday: 1,
|
|
310
|
-
mon: 1,
|
|
311
|
-
tuesday: 2,
|
|
312
|
-
tue: 2,
|
|
313
|
-
tues: 2,
|
|
314
|
-
wednesday: 3,
|
|
315
|
-
wed: 3,
|
|
316
|
-
thursday: 4,
|
|
317
|
-
thu: 4,
|
|
318
|
-
thur: 4,
|
|
319
|
-
thurs: 4,
|
|
320
|
-
friday: 5,
|
|
321
|
-
fri: 5,
|
|
322
|
-
saturday: 6,
|
|
323
|
-
sat: 6
|
|
324
|
-
};
|
|
325
1
|
const FLUID_CLASS = 'cld-fluid';
|
|
326
2
|
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
* @returns {number|null} 0-6, or null if invalid
|
|
331
|
-
*/
|
|
332
|
-
const parseDay = day => {
|
|
333
|
-
if (typeof day !== 'string') return null;
|
|
334
|
-
const key = day.toLowerCase().trim();
|
|
335
|
-
return DAY_MAP[key] ?? null;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Parse "HH:mm" string to minutes since midnight.
|
|
340
|
-
* @param {string} timeStr - "09:00" or "17:30"
|
|
341
|
-
* @returns {number|null} minutes, or null if invalid
|
|
342
|
-
*/
|
|
343
|
-
const parseTime = timeStr => {
|
|
344
|
-
if (typeof timeStr !== 'string') return null;
|
|
345
|
-
const match = timeStr.trim().match(/^(\d{1,2}):(\d{2})$/);
|
|
346
|
-
if (!match) return null;
|
|
347
|
-
const h = parseInt(match[1], 10);
|
|
348
|
-
const m = parseInt(match[2], 10);
|
|
349
|
-
if (h < 0 || h > 23 || m < 0 || m > 59) return null;
|
|
350
|
-
return h * 60 + m;
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Check if a date falls within any configured weekly slot (local time).
|
|
355
|
-
* @param {{ weekly?: Array<{ day: string, start: string, duration: number }> }} schedule - schedule config
|
|
356
|
-
* @param {Date} date - date to check (uses local time)
|
|
357
|
-
* @returns {boolean} true if within a slot
|
|
358
|
-
*/
|
|
359
|
-
const isWithinSchedule = (schedule, date) => {
|
|
360
|
-
const weekly = schedule?.weekly;
|
|
361
|
-
if (!Array.isArray(weekly) || weekly.length === 0) return true;
|
|
362
|
-
const WEEK = 7 * 1440;
|
|
363
|
-
const nowInWeek = date.getDay() * 1440 + date.getHours() * 60 + date.getMinutes();
|
|
364
|
-
for (const slot of weekly) {
|
|
365
|
-
const slotDay = parseDay(slot.day);
|
|
366
|
-
if (slotDay === null) continue;
|
|
367
|
-
const startMin = parseTime(slot.start);
|
|
368
|
-
if (startMin === null || typeof slot.duration !== 'number' || slot.duration <= 0) continue;
|
|
369
|
-
const slotStart = slotDay * 1440 + startMin;
|
|
370
|
-
const durationMin = slot.duration * 60;
|
|
371
|
-
const elapsed = (nowInWeek - slotStart + WEEK) % WEEK;
|
|
372
|
-
if (elapsed < durationMin) return true;
|
|
373
|
-
}
|
|
374
|
-
return false;
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Resolve video element by id or return element. No videojs.
|
|
379
|
-
* @param {string|HTMLElement} elem - Element id (with or without #) or video element
|
|
380
|
-
* @returns {HTMLVideoElement}
|
|
381
|
-
*/
|
|
382
|
-
const getElementForSchedule = elem => {
|
|
3
|
+
/** Same condition as `getPosterUrl` in `poster-url.js` (explicit string `poster`); skips the `cld-poster-url` async chunk. */
|
|
4
|
+
const hasExplicitPoster = options => typeof options?.poster === 'string' && options.poster.length > 0;
|
|
5
|
+
const getVideoElement = elem => {
|
|
383
6
|
if (typeof elem === 'string') {
|
|
384
7
|
let id = elem;
|
|
385
8
|
if (id.indexOf('#') === 0) id = id.slice(1);
|
|
386
9
|
try {
|
|
387
|
-
elem = document.querySelector(`#${
|
|
10
|
+
elem = document.querySelector(`#${CSS.escape(id)}`);
|
|
388
11
|
} catch {
|
|
389
12
|
elem = null;
|
|
390
13
|
}
|
|
@@ -394,45 +17,171 @@ const getElementForSchedule = elem => {
|
|
|
394
17
|
if (elem.tagName !== 'VIDEO') throw new Error('Element is not a video tag.');
|
|
395
18
|
return elem;
|
|
396
19
|
};
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Hide video, show poster image overlay. Keeps video in DOM for load().
|
|
400
|
-
* @param {HTMLVideoElement} videoElement
|
|
401
|
-
* @param {string} posterUrl
|
|
402
|
-
* @param {object} options - fluid, width, height, etc.
|
|
403
|
-
* @returns {{ img: HTMLImageElement, container: HTMLElement, videoElement: HTMLVideoElement }}
|
|
404
|
-
*/
|
|
405
|
-
const renderScheduleImage = function (videoElement, posterUrl) {
|
|
20
|
+
const preparePlayerPlaceholder = function (videoElement, posterUrl) {
|
|
406
21
|
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
22
|
+
const hadControls = videoElement.hasAttribute('controls');
|
|
23
|
+
videoElement.poster = posterUrl;
|
|
24
|
+
videoElement.preload = 'none';
|
|
25
|
+
videoElement.controls = false;
|
|
26
|
+
videoElement.removeAttribute('controls');
|
|
407
27
|
const fluid = options.fluid !== false;
|
|
408
|
-
const parent = videoElement.parentNode;
|
|
409
|
-
const container = document.createElement('div');
|
|
410
|
-
container.className = 'cld-schedule-poster-container';
|
|
411
|
-
container.style.cssText = 'position:relative;width:100%;height:100%;';
|
|
412
|
-
const img = document.createElement('img');
|
|
413
|
-
img.src = posterUrl;
|
|
414
|
-
img.alt = '';
|
|
415
|
-
img.setAttribute('data-cld-schedule-poster', 'true');
|
|
416
|
-
img.style.cssText = 'display:block;width:100%;height:100%;object-fit:contain;';
|
|
417
28
|
if (fluid) {
|
|
418
|
-
|
|
419
|
-
|
|
29
|
+
videoElement.classList.add(FLUID_CLASS);
|
|
30
|
+
}
|
|
31
|
+
if (options.width) videoElement.setAttribute('width', String(options.width));
|
|
32
|
+
if (options.height) videoElement.setAttribute('height', String(options.height));
|
|
33
|
+
const ar = options?.sourceOptions?.aspectRatio || options?.aspectRatio;
|
|
34
|
+
if (typeof ar === 'string' && ar.includes(':')) {
|
|
35
|
+
const parts = ar.split(':').map(x => parseInt(x.trim(), 10));
|
|
36
|
+
if (parts.length === 2 && parts[0] > 0 && parts[1] > 0) {
|
|
37
|
+
videoElement.style.aspectRatio = `${parts[0]} / ${parts[1]}`;
|
|
38
|
+
}
|
|
420
39
|
}
|
|
421
|
-
if (options.width) container.style.width = `${options.width}px`;
|
|
422
|
-
if (options.height) container.style.height = `${options.height}px`;
|
|
423
|
-
videoElement.style.display = 'none';
|
|
424
|
-
container.appendChild(img);
|
|
425
|
-
parent.insertBefore(container, videoElement);
|
|
426
40
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
41
|
+
videoElement,
|
|
42
|
+
hadControls
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const loadPlayer = _ref => {
|
|
46
|
+
let {
|
|
47
|
+
overlayRoot,
|
|
48
|
+
videoElement,
|
|
49
|
+
options,
|
|
50
|
+
ready
|
|
51
|
+
} = _ref;
|
|
52
|
+
if (overlayRoot?.parentNode) {
|
|
53
|
+
overlayRoot.replaceWith(videoElement);
|
|
54
|
+
}
|
|
55
|
+
return import('./video-player.js').then(function (n) { return n.v; }).then(m => m.createVideoPlayer(videoElement, options, ready));
|
|
56
|
+
};
|
|
57
|
+
const LAZY_PLAYER_CLASS = 'cld-lazy-player';
|
|
58
|
+
const isLightSkin = (videoElement, options) => {
|
|
59
|
+
const cls = videoElement.className || '';
|
|
60
|
+
return cls.indexOf('cld-video-player-skin-light') > -1 || options?.skin === 'light';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Matches Video.js BigPlayButton DOM structure. */
|
|
64
|
+
const createBigPlayButton = () => {
|
|
65
|
+
const playBtn = document.createElement('button');
|
|
66
|
+
playBtn.type = 'button';
|
|
67
|
+
playBtn.className = 'vjs-big-play-button';
|
|
68
|
+
playBtn.setAttribute('aria-disabled', 'false');
|
|
69
|
+
playBtn.title = 'Play Video';
|
|
70
|
+
playBtn.setAttribute('aria-label', 'Play Video');
|
|
71
|
+
const icon = document.createElement('span');
|
|
72
|
+
icon.className = 'vjs-icon-placeholder';
|
|
73
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
74
|
+
playBtn.appendChild(icon);
|
|
75
|
+
return playBtn;
|
|
76
|
+
};
|
|
77
|
+
const shouldUseLazyBootstrap = options => !!options?.lazy;
|
|
78
|
+
const shouldLoadOnScroll = lazy => lazy && typeof lazy === 'object' && lazy.loadOnScroll === true;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Renders the lazy placeholder (poster, big-play) before the main player chunk loads.
|
|
82
|
+
*
|
|
83
|
+
* @param {string|HTMLVideoElement} elem
|
|
84
|
+
* @param {object} options
|
|
85
|
+
* @param {function} [ready] - Passed through when the full player loads.
|
|
86
|
+
* @returns {Promise<{ source: function, loadPlayer: function }>}
|
|
87
|
+
*/
|
|
88
|
+
const lazyBootstrap = async (elem, options, ready) => {
|
|
89
|
+
const videoElement = getVideoElement(elem);
|
|
90
|
+
const posterUrl = hasExplicitPoster(options) ? options.poster : (await import(/* webpackChunkName: "cld-poster-url" */'./poster-url.js')).getPosterUrl(options);
|
|
91
|
+
const loadOnScroll = shouldLoadOnScroll(options.lazy);
|
|
92
|
+
const {
|
|
93
|
+
hadControls
|
|
94
|
+
} = preparePlayerPlaceholder(videoElement, posterUrl, {
|
|
95
|
+
fluid: options?.fluid !== false,
|
|
96
|
+
width: options?.width,
|
|
97
|
+
height: options?.height,
|
|
98
|
+
sourceOptions: options?.sourceOptions,
|
|
99
|
+
aspectRatio: options?.aspectRatio
|
|
100
|
+
});
|
|
101
|
+
const light = isLightSkin(videoElement, options);
|
|
102
|
+
const overlayRoot = document.createElement('div');
|
|
103
|
+
overlayRoot.classList.add('cld-video-player', 'video-js', LAZY_PLAYER_CLASS);
|
|
104
|
+
overlayRoot.classList.add(light ? 'cld-video-player-skin-light' : 'cld-video-player-skin-dark');
|
|
105
|
+
const colors = options?.colors;
|
|
106
|
+
if (colors) {
|
|
107
|
+
if (colors.base) overlayRoot.style.setProperty('--color-base', colors.base);
|
|
108
|
+
if (colors.accent) overlayRoot.style.setProperty('--color-accent', colors.accent);
|
|
109
|
+
if (colors.text) overlayRoot.style.setProperty('--color-text', colors.text);
|
|
110
|
+
}
|
|
111
|
+
videoElement.parentNode.insertBefore(overlayRoot, videoElement);
|
|
112
|
+
overlayRoot.appendChild(videoElement);
|
|
113
|
+
const playBtn = createBigPlayButton();
|
|
114
|
+
overlayRoot.appendChild(playBtn);
|
|
115
|
+
let loadPromise = null;
|
|
116
|
+
let observer = null;
|
|
117
|
+
const teardownActivation = () => {
|
|
118
|
+
playBtn.removeEventListener('click', onPlayClick);
|
|
119
|
+
videoElement.removeEventListener('click', onVideoClick);
|
|
120
|
+
if (observer) {
|
|
121
|
+
observer.disconnect();
|
|
122
|
+
observer = null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const activatePlayer = function () {
|
|
126
|
+
let activationOpts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
127
|
+
if (loadPromise) {
|
|
128
|
+
return loadPromise;
|
|
129
|
+
}
|
|
130
|
+
teardownActivation();
|
|
131
|
+
const autoplayFromUserGesture = activationOpts.autoplayFromUserGesture === true;
|
|
132
|
+
if (hadControls) videoElement.setAttribute('controls', '');
|
|
133
|
+
const wrappedReady = autoplayFromUserGesture ? p => {
|
|
134
|
+
p.play();
|
|
135
|
+
if (ready) ready(p);
|
|
136
|
+
} : ready;
|
|
137
|
+
const playerOptions = Object.assign({}, options);
|
|
138
|
+
delete playerOptions.lazy;
|
|
139
|
+
loadPromise = loadPlayer({
|
|
140
|
+
overlayRoot,
|
|
141
|
+
videoElement,
|
|
142
|
+
options: playerOptions,
|
|
143
|
+
ready: wrappedReady
|
|
144
|
+
});
|
|
145
|
+
return loadPromise;
|
|
430
146
|
};
|
|
147
|
+
function onPlayClick(e) {
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
activatePlayer({
|
|
150
|
+
autoplayFromUserGesture: true
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function onVideoClick() {
|
|
154
|
+
activatePlayer({
|
|
155
|
+
autoplayFromUserGesture: true
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
playBtn.addEventListener('click', onPlayClick);
|
|
159
|
+
videoElement.addEventListener('click', onVideoClick);
|
|
160
|
+
if (loadOnScroll && typeof IntersectionObserver !== 'undefined') {
|
|
161
|
+
observer = new IntersectionObserver(entries => {
|
|
162
|
+
entries.forEach(entry => {
|
|
163
|
+
if (entry.isIntersecting) {
|
|
164
|
+
activatePlayer({});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}, {
|
|
168
|
+
rootMargin: '0px',
|
|
169
|
+
threshold: 0.25
|
|
170
|
+
});
|
|
171
|
+
observer.observe(videoElement);
|
|
172
|
+
}
|
|
173
|
+
const stub = {
|
|
174
|
+
source: () => stub,
|
|
175
|
+
loadPlayer: () => activatePlayer({
|
|
176
|
+
autoplayFromUserGesture: true
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
return stub;
|
|
431
180
|
};
|
|
432
181
|
|
|
433
182
|
const createAsyncPlayer = async (id, playerOptions, ready, createFn) => {
|
|
434
183
|
const mergedOptions = Object.assign({}, playerOptions);
|
|
435
|
-
const videoElement =
|
|
184
|
+
const videoElement = getVideoElement(id);
|
|
436
185
|
const opts = await (async () => {
|
|
437
186
|
try {
|
|
438
187
|
const {
|
|
@@ -444,8 +193,17 @@ const createAsyncPlayer = async (id, playerOptions, ready, createFn) => {
|
|
|
444
193
|
return mergedOptions;
|
|
445
194
|
}
|
|
446
195
|
})();
|
|
447
|
-
if (
|
|
448
|
-
|
|
196
|
+
if (opts?.schedule?.weekly) {
|
|
197
|
+
const {
|
|
198
|
+
shouldUseScheduleBootstrap,
|
|
199
|
+
scheduleBootstrap
|
|
200
|
+
} = await import('./schedule.js');
|
|
201
|
+
if (shouldUseScheduleBootstrap(opts)) {
|
|
202
|
+
return scheduleBootstrap(id, opts, ready);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (shouldUseLazyBootstrap(opts)) {
|
|
206
|
+
return lazyBootstrap(id, opts, ready);
|
|
449
207
|
}
|
|
450
208
|
return createFn(videoElement, opts, ready);
|
|
451
209
|
};
|
|
@@ -466,4 +224,4 @@ const setupCloudinaryGlobal = methods => {
|
|
|
466
224
|
return cloudinary;
|
|
467
225
|
};
|
|
468
226
|
|
|
469
|
-
export {
|
|
227
|
+
export { createAsyncPlayer as a, createMultipleSync as b, createMultiplePlayers as c, getVideoElement as g, loadPlayer as l, preparePlayerPlaceholder as p, setupCloudinaryGlobal as s };
|
package/lib/player.js
CHANGED
|
@@ -2,7 +2,8 @@ export { player as default } from './index.js';
|
|
|
2
2
|
import './_videojs-proxy.js';
|
|
3
3
|
import './_commonjsHelpers.js';
|
|
4
4
|
import './video-player.js';
|
|
5
|
-
import './
|
|
5
|
+
import './cloudinary-config-from-options.js';
|
|
6
6
|
import './video-player.const.js';
|
|
7
7
|
import './validators-functions.js';
|
|
8
|
+
import './cloudinary-url-prefix.js';
|
|
8
9
|
import './player-api.js';
|
package/lib/playlist.js
CHANGED
|
@@ -2,10 +2,10 @@ import { j as isPlainObject, k as camelCase, V as VideoSource, P as PLAYER_EVENT
|
|
|
2
2
|
import { i as isInteger, P as PlaylistLayout } from './playlist-panel.js';
|
|
3
3
|
import { _ as _vjs } from './_videojs-proxy.js';
|
|
4
4
|
import './_commonjsHelpers.js';
|
|
5
|
-
import './
|
|
5
|
+
import './cloudinary-config-from-options.js';
|
|
6
6
|
import './video-player.const.js';
|
|
7
7
|
import './validators-functions.js';
|
|
8
|
-
import './
|
|
8
|
+
import './cloudinary-url-prefix.js';
|
|
9
9
|
import './toNumber.js';
|
|
10
10
|
|
|
11
11
|
// https://github.com/csnover/js-iso8601/blob/master/iso8601.js
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { g as getCloudinaryUrlPrefix } from './cloudinary-url-prefix.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cloudinary video poster URLs for bootstrap paths (lazy shell, schedule image) where the full player is not loaded.
|
|
5
|
+
* Default delivery matches VideoSource poster defaults: video resource, JPG still.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} cloudName
|
|
10
|
+
* @param {string} publicId
|
|
11
|
+
* @param {object} [cloudinaryConfig] - Same shape as player `cloudinaryConfig` (e.g. private_cdn, cname, secure).
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
const buildPosterUrl = function (cloudName, publicId) {
|
|
15
|
+
let cloudinaryConfig = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
16
|
+
const config = {
|
|
17
|
+
cloud_name: cloudName || cloudinaryConfig.cloud_name,
|
|
18
|
+
...cloudinaryConfig,
|
|
19
|
+
secure: cloudinaryConfig.secure ?? true
|
|
20
|
+
};
|
|
21
|
+
const prefix = getCloudinaryUrlPrefix(config);
|
|
22
|
+
return `${prefix}/video/upload/${publicId}.jpg`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the poster URL for lazy bootstrap from player options.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} options - Player options (`poster`, `cloudName` / `cloud_name`, `publicId` / `sourceOptions.publicId`, `cloudinaryConfig`).
|
|
29
|
+
* @returns {string}
|
|
30
|
+
* @throws {Error} When no explicit `poster` string and cloud name + public id are missing.
|
|
31
|
+
*/
|
|
32
|
+
const getPosterUrl = options => {
|
|
33
|
+
if (typeof options?.poster === 'string' && options.poster.length > 0) {
|
|
34
|
+
return options.poster;
|
|
35
|
+
}
|
|
36
|
+
const cloudName = options?.cloudName || options?.cloud_name || options?.cloudinaryConfig?.cloud_name;
|
|
37
|
+
const publicId = options?.publicId || options?.sourceOptions?.publicId;
|
|
38
|
+
if (cloudName && publicId) {
|
|
39
|
+
return buildPosterUrl(cloudName, publicId, options?.cloudinaryConfig || {
|
|
40
|
+
cloud_name: cloudName
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
throw new Error('lazy requires a poster URL or cloudName and publicId');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { buildPosterUrl, getPosterUrl };
|