@smartimpact-it/modern-video-embed 2.0.6 → 2.0.8
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/components/BaseVideoEmbed.d.ts +27 -5
- package/dist/components/BaseVideoEmbed.d.ts.map +1 -1
- package/dist/components/BaseVideoEmbed.js +70 -0
- package/dist/components/BaseVideoEmbed.js.map +1 -1
- package/dist/components/VideoEmbed.d.ts +6 -0
- package/dist/components/VideoEmbed.d.ts.map +1 -1
- package/dist/components/VideoEmbed.js +45 -40
- package/dist/components/VideoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.d.ts +7 -5
- package/dist/components/VimeoEmbed.d.ts.map +1 -1
- package/dist/components/VimeoEmbed.js +67 -97
- package/dist/components/VimeoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.min.js +1 -1
- package/dist/components/YouTubeEmbed.d.ts +7 -6
- package/dist/components/YouTubeEmbed.d.ts.map +1 -1
- package/dist/components/YouTubeEmbed.js +65 -108
- package/dist/components/YouTubeEmbed.js.map +1 -1
- package/dist/components/YouTubeEmbed.min.js +1 -1
- package/dist/css/components.css +68 -42
- package/dist/css/components.css.map +1 -1
- package/dist/css/components.min.css +1 -1
- package/dist/css/main.css +68 -42
- package/dist/css/main.css.map +1 -1
- package/dist/css/main.min.css +1 -1
- package/dist/utils/APILoader.d.ts +55 -0
- package/dist/utils/APILoader.d.ts.map +1 -0
- package/dist/utils/APILoader.js +148 -0
- package/dist/utils/APILoader.js.map +1 -0
- package/package.json +1 -1
- package/src/components/BaseVideoEmbed.ts +107 -6
- package/src/components/VideoEmbed.ts +51 -42
- package/src/components/VimeoEmbed.ts +71 -113
- package/src/components/YouTubeEmbed.ts +71 -130
- package/src/styles/_embed-base.scss +35 -15
- package/src/styles/video-embed.scss +18 -0
- package/src/utils/APILoader.ts +192 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for loading external API scripts (YouTube, Vimeo, etc.)
|
|
3
|
+
* Provides retry mechanism, timeout handling, and deduplication.
|
|
4
|
+
*/
|
|
5
|
+
export interface APILoaderConfig {
|
|
6
|
+
/** URL of the script to load */
|
|
7
|
+
scriptUrl: string;
|
|
8
|
+
/** Function to check if the API is ready (e.g., checking global variables) */
|
|
9
|
+
globalCheck: () => boolean;
|
|
10
|
+
/** Optional global callback function name (e.g., 'onYouTubeIframeAPIReady') */
|
|
11
|
+
globalCallback?: string;
|
|
12
|
+
/** Timeout in milliseconds (default: 10000) */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/** Maximum retry attempts (default: 3) */
|
|
15
|
+
maxRetries?: number;
|
|
16
|
+
/** Delay between retries in milliseconds (default: 2000) */
|
|
17
|
+
retryDelay?: number;
|
|
18
|
+
/** Polling interval for checking API readiness in milliseconds (default: 100) */
|
|
19
|
+
pollInterval?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* APILoader class handles loading external scripts with retry logic and deduplication.
|
|
23
|
+
* Multiple components can request the same API without duplicate loads.
|
|
24
|
+
*/
|
|
25
|
+
export declare class APILoader {
|
|
26
|
+
private static loadingPromises;
|
|
27
|
+
private static loadedScripts;
|
|
28
|
+
private static readyApis;
|
|
29
|
+
/**
|
|
30
|
+
* Load an external API script with retry mechanism.
|
|
31
|
+
* @param key - Unique identifier for this API (e.g., 'youtube', 'vimeo')
|
|
32
|
+
* @param config - Configuration for loading the script
|
|
33
|
+
* @returns Promise that resolves when the API is ready
|
|
34
|
+
*/
|
|
35
|
+
static loadScript(key: string, config: APILoaderConfig): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Internal method to load script with retry logic.
|
|
38
|
+
*/
|
|
39
|
+
private static loadWithRetry;
|
|
40
|
+
/**
|
|
41
|
+
* Internal method to load script once.
|
|
42
|
+
* Handles deduplication and manages loading state.
|
|
43
|
+
*/
|
|
44
|
+
private static loadScriptOnce;
|
|
45
|
+
/**
|
|
46
|
+
* Check if an API is ready without loading it.
|
|
47
|
+
* Useful for testing or conditional logic.
|
|
48
|
+
*/
|
|
49
|
+
static isReady(key: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Reset the loader state (mainly for testing).
|
|
52
|
+
*/
|
|
53
|
+
static reset(): void;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=APILoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"APILoader.d.ts","sourceRoot":"","sources":["../../src/utils/APILoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,eAAe,CAAoC;IAClE,OAAO,CAAC,MAAM,CAAC,aAAa,CAAqB;IACjD,OAAO,CAAC,MAAM,CAAC,SAAS,CAAqB;IAE7C;;;;;OAKG;WACU,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5E;;OAEG;mBACkB,aAAa;IA2BlC;;;OAGG;mBACkB,cAAc;IAkGnC;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for loading external API scripts (YouTube, Vimeo, etc.)
|
|
3
|
+
* Provides retry mechanism, timeout handling, and deduplication.
|
|
4
|
+
*/
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* APILoader class handles loading external scripts with retry logic and deduplication.
|
|
16
|
+
* Multiple components can request the same API without duplicate loads.
|
|
17
|
+
*/
|
|
18
|
+
export class APILoader {
|
|
19
|
+
/**
|
|
20
|
+
* Load an external API script with retry mechanism.
|
|
21
|
+
* @param key - Unique identifier for this API (e.g., 'youtube', 'vimeo')
|
|
22
|
+
* @param config - Configuration for loading the script
|
|
23
|
+
* @returns Promise that resolves when the API is ready
|
|
24
|
+
*/
|
|
25
|
+
static loadScript(key, config) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const { maxRetries = 3, retryDelay = 2000 } = config;
|
|
28
|
+
return this.loadWithRetry(key, config, 0, maxRetries, retryDelay);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Internal method to load script with retry logic.
|
|
33
|
+
*/
|
|
34
|
+
static loadWithRetry(key, config, retries, maxRetries, retryDelay) {
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
try {
|
|
37
|
+
yield this.loadScriptOnce(key, config);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (retries < maxRetries) {
|
|
41
|
+
console.warn(`${key} API load failed, retrying (${retries + 1}/${maxRetries})...`);
|
|
42
|
+
yield new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
43
|
+
return this.loadWithRetry(key, config, retries + 1, maxRetries, retryDelay);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Internal method to load script once.
|
|
51
|
+
* Handles deduplication and manages loading state.
|
|
52
|
+
*/
|
|
53
|
+
static loadScriptOnce(key, config) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
const { scriptUrl, globalCheck, globalCallback, timeout = 10000, pollInterval = 100, } = config;
|
|
56
|
+
// Already ready
|
|
57
|
+
if (this.readyApis.has(key)) {
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
}
|
|
60
|
+
// Already loading - wait for existing promise
|
|
61
|
+
if (this.loadingPromises.has(key)) {
|
|
62
|
+
return this.loadingPromises.get(key);
|
|
63
|
+
}
|
|
64
|
+
// Create new loading promise
|
|
65
|
+
const promise = new Promise((resolve, reject) => {
|
|
66
|
+
const timeoutId = setTimeout(() => {
|
|
67
|
+
cleanup();
|
|
68
|
+
this.loadingPromises.delete(key);
|
|
69
|
+
reject(new Error(`${key} API loading timeout. The API script may be blocked by an ad blocker or network issue.`));
|
|
70
|
+
}, timeout);
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
clearTimeout(timeoutId);
|
|
73
|
+
if (globalCallback) {
|
|
74
|
+
delete window[globalCallback];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// Add script if not already loaded
|
|
78
|
+
if (!this.loadedScripts.has(key)) {
|
|
79
|
+
const script = document.createElement("script");
|
|
80
|
+
script.src = scriptUrl;
|
|
81
|
+
script.onerror = () => {
|
|
82
|
+
cleanup();
|
|
83
|
+
this.loadingPromises.delete(key);
|
|
84
|
+
reject(new Error(`Failed to load ${key} API script. Please check your network connection or disable ad blockers.`));
|
|
85
|
+
};
|
|
86
|
+
// If there's a global callback (YouTube style)
|
|
87
|
+
if (globalCallback) {
|
|
88
|
+
window[globalCallback] = () => {
|
|
89
|
+
this.readyApis.add(key);
|
|
90
|
+
this.loadingPromises.delete(key);
|
|
91
|
+
cleanup();
|
|
92
|
+
resolve();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Poll for readiness (Vimeo style)
|
|
97
|
+
script.onload = () => {
|
|
98
|
+
const checkReady = setInterval(() => {
|
|
99
|
+
if (globalCheck()) {
|
|
100
|
+
this.readyApis.add(key);
|
|
101
|
+
this.loadingPromises.delete(key);
|
|
102
|
+
clearInterval(checkReady);
|
|
103
|
+
cleanup();
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
}, pollInterval);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
document.head.appendChild(script);
|
|
110
|
+
this.loadedScripts.add(key);
|
|
111
|
+
}
|
|
112
|
+
else if (!this.readyApis.has(key)) {
|
|
113
|
+
// Script already added but not ready yet - poll for readiness
|
|
114
|
+
const checkReady = setInterval(() => {
|
|
115
|
+
if (globalCheck()) {
|
|
116
|
+
this.readyApis.add(key);
|
|
117
|
+
this.loadingPromises.delete(key);
|
|
118
|
+
clearInterval(checkReady);
|
|
119
|
+
cleanup();
|
|
120
|
+
resolve();
|
|
121
|
+
}
|
|
122
|
+
}, pollInterval);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.loadingPromises.set(key, promise);
|
|
126
|
+
return promise;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if an API is ready without loading it.
|
|
131
|
+
* Useful for testing or conditional logic.
|
|
132
|
+
*/
|
|
133
|
+
static isReady(key) {
|
|
134
|
+
return this.readyApis.has(key);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Reset the loader state (mainly for testing).
|
|
138
|
+
*/
|
|
139
|
+
static reset() {
|
|
140
|
+
this.loadingPromises.clear();
|
|
141
|
+
this.loadedScripts.clear();
|
|
142
|
+
this.readyApis.clear();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
APILoader.loadingPromises = new Map();
|
|
146
|
+
APILoader.loadedScripts = new Set();
|
|
147
|
+
APILoader.readyApis = new Set();
|
|
148
|
+
//# sourceMappingURL=APILoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"APILoader.js","sourceRoot":"","sources":["../../src/utils/APILoader.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartimpact-it/modern-video-embed",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Modern YouTube and Vimeo embed web components with lazy loading, accessibility, quality control, and comprehensive API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -35,6 +35,15 @@ export abstract class BaseVideoEmbed extends HTMLElement {
|
|
|
35
35
|
protected abstract reinitializePlayer(): void;
|
|
36
36
|
protected abstract destroyPlayer(): void;
|
|
37
37
|
protected abstract getComponentName(): string;
|
|
38
|
+
protected abstract handlePlay(): void;
|
|
39
|
+
protected abstract handlePause(): void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Abstract methods for component-specific attribute handling
|
|
43
|
+
*/
|
|
44
|
+
protected abstract syncMutedState(): void;
|
|
45
|
+
protected abstract syncControlsState(): void;
|
|
46
|
+
protected abstract syncPosterState(): void;
|
|
38
47
|
|
|
39
48
|
/**
|
|
40
49
|
* Logging utilities
|
|
@@ -89,6 +98,72 @@ export abstract class BaseVideoEmbed extends HTMLElement {
|
|
|
89
98
|
this.updatingAttribute = false;
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Common attribute change handler for attributes shared across all components.
|
|
103
|
+
* Returns true if the attribute was handled, false otherwise.
|
|
104
|
+
* Subclasses should call this first, then handle component-specific attributes.
|
|
105
|
+
*/
|
|
106
|
+
protected handleCommonAttributeChange(
|
|
107
|
+
name: string,
|
|
108
|
+
_oldValue: string | null,
|
|
109
|
+
newValue: string | null,
|
|
110
|
+
): boolean {
|
|
111
|
+
switch (name) {
|
|
112
|
+
case "autoplay":
|
|
113
|
+
this._autoplay = newValue !== null;
|
|
114
|
+
if (
|
|
115
|
+
!this._playing &&
|
|
116
|
+
this._autoplay &&
|
|
117
|
+
!this._lazy &&
|
|
118
|
+
this.initialized
|
|
119
|
+
) {
|
|
120
|
+
this.play();
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
|
|
124
|
+
case "lazy":
|
|
125
|
+
this._lazy = newValue !== null;
|
|
126
|
+
return true;
|
|
127
|
+
|
|
128
|
+
case "muted":
|
|
129
|
+
this._muted = newValue !== null;
|
|
130
|
+
this.syncMutedState();
|
|
131
|
+
return true;
|
|
132
|
+
|
|
133
|
+
case "controls":
|
|
134
|
+
this._controls = newValue !== null;
|
|
135
|
+
this.syncControlsState();
|
|
136
|
+
return true;
|
|
137
|
+
|
|
138
|
+
case "poster":
|
|
139
|
+
this._poster = newValue || "";
|
|
140
|
+
this.syncPosterState();
|
|
141
|
+
return true;
|
|
142
|
+
|
|
143
|
+
case "background":
|
|
144
|
+
this._background = newValue !== null;
|
|
145
|
+
this.updateBackgroundMode();
|
|
146
|
+
return true;
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
return false; // Not handled by base class
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Public play method - subclasses can override
|
|
155
|
+
*/
|
|
156
|
+
public play(): void {
|
|
157
|
+
this.handlePlay();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Public pause method - subclasses can override
|
|
162
|
+
*/
|
|
163
|
+
public pause(): void {
|
|
164
|
+
this.handlePause();
|
|
165
|
+
}
|
|
166
|
+
|
|
92
167
|
/**
|
|
93
168
|
* Error message display (common across all components)
|
|
94
169
|
*/
|
|
@@ -148,6 +223,38 @@ export abstract class BaseVideoEmbed extends HTMLElement {
|
|
|
148
223
|
}
|
|
149
224
|
}
|
|
150
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Automatically set aspect ratio CSS custom properties
|
|
228
|
+
* This allows background videos to maintain correct proportions
|
|
229
|
+
*/
|
|
230
|
+
protected setAspectRatio(width: number, height: number): void {
|
|
231
|
+
if (width > 0 && height > 0) {
|
|
232
|
+
// Calculate GCD for simplest ratio
|
|
233
|
+
const gcd = (a: number, b: number): number =>
|
|
234
|
+
b === 0 ? a : gcd(b, a % b);
|
|
235
|
+
const divisor = gcd(width, height);
|
|
236
|
+
const aspectWidth = width / divisor;
|
|
237
|
+
const aspectHeight = height / divisor;
|
|
238
|
+
|
|
239
|
+
this.style.setProperty("--si-embed-aspect-width", aspectWidth.toString());
|
|
240
|
+
this.style.setProperty(
|
|
241
|
+
"--si-embed-aspect-height",
|
|
242
|
+
aspectHeight.toString(),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Also update the padding-top percentage for normal mode
|
|
246
|
+
const aspectRatioPercent = (height / width) * 100;
|
|
247
|
+
this.style.setProperty(
|
|
248
|
+
"--si-embed-video-aspect-ratio",
|
|
249
|
+
`${aspectRatioPercent}%`,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
this.log(
|
|
253
|
+
`Aspect ratio auto-detected: ${aspectWidth}:${aspectHeight} (${width}x${height})`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
151
258
|
/**
|
|
152
259
|
* Custom controls creation (similar pattern across components)
|
|
153
260
|
*/
|
|
@@ -189,12 +296,6 @@ export abstract class BaseVideoEmbed extends HTMLElement {
|
|
|
189
296
|
this.appendChild(buttonOverlay);
|
|
190
297
|
}
|
|
191
298
|
|
|
192
|
-
/**
|
|
193
|
-
* Play/Pause handlers - subclasses override with platform-specific logic
|
|
194
|
-
*/
|
|
195
|
-
protected abstract handlePlay(): void;
|
|
196
|
-
protected abstract handlePause(): void;
|
|
197
|
-
|
|
198
299
|
/**
|
|
199
300
|
* Lazy loading setup (common pattern)
|
|
200
301
|
*/
|
|
@@ -37,6 +37,12 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
37
37
|
|
|
38
38
|
this.log(`Attribute changed - ${name}: ${oldValue} -> ${newValue}`);
|
|
39
39
|
|
|
40
|
+
// Try common attribute handling first
|
|
41
|
+
if (this.handleCommonAttributeChange(name, oldValue, newValue)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle VideoEmbed-specific attributes
|
|
40
46
|
switch (name) {
|
|
41
47
|
case "url":
|
|
42
48
|
this.#url = newValue || "";
|
|
@@ -44,44 +50,6 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
44
50
|
this.reinitializePlayer();
|
|
45
51
|
}
|
|
46
52
|
break;
|
|
47
|
-
case "autoplay":
|
|
48
|
-
this._autoplay = newValue !== null;
|
|
49
|
-
if (
|
|
50
|
-
!this._playing &&
|
|
51
|
-
this._autoplay &&
|
|
52
|
-
!this._lazy &&
|
|
53
|
-
this.initialized
|
|
54
|
-
) {
|
|
55
|
-
this.play();
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
case "controls":
|
|
59
|
-
this._controls = newValue !== null;
|
|
60
|
-
if (this.video) {
|
|
61
|
-
this.video.controls = this._controls;
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
case "lazy":
|
|
65
|
-
this._lazy = newValue !== null;
|
|
66
|
-
break;
|
|
67
|
-
case "muted":
|
|
68
|
-
this._muted = newValue !== null;
|
|
69
|
-
if (this.video) {
|
|
70
|
-
this.video.muted = this._muted;
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
73
|
-
case "poster":
|
|
74
|
-
this._poster = newValue || "";
|
|
75
|
-
if (this._lazy && !this.video) {
|
|
76
|
-
this.showPoster();
|
|
77
|
-
} else if (this.video) {
|
|
78
|
-
this.video.poster = this._poster;
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
case "background":
|
|
82
|
-
this._background = newValue !== null;
|
|
83
|
-
this.updateBackgroundMode();
|
|
84
|
-
break;
|
|
85
53
|
case "loop":
|
|
86
54
|
this.#loop = newValue !== null;
|
|
87
55
|
if (this.video) {
|
|
@@ -133,6 +101,11 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
133
101
|
return this._controls;
|
|
134
102
|
}
|
|
135
103
|
set controls(value: boolean) {
|
|
104
|
+
// Background mode videos must not have controls
|
|
105
|
+
if (this._background && value) {
|
|
106
|
+
this.warn("Cannot enable controls on background video");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
136
109
|
this._controls = value;
|
|
137
110
|
this.reflectBooleanAttribute("controls", value);
|
|
138
111
|
if (this.video) {
|
|
@@ -152,6 +125,11 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
152
125
|
return this._muted;
|
|
153
126
|
}
|
|
154
127
|
set muted(value: boolean) {
|
|
128
|
+
// Background mode videos must be muted
|
|
129
|
+
if (this._background && !value) {
|
|
130
|
+
this.warn("Cannot unmute background video");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
155
133
|
this._muted = value;
|
|
156
134
|
this.reflectBooleanAttribute("muted", value);
|
|
157
135
|
if (this.video) {
|
|
@@ -489,6 +467,29 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
489
467
|
}
|
|
490
468
|
}
|
|
491
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Sync methods for common attributes (required by BaseVideoEmbed)
|
|
472
|
+
*/
|
|
473
|
+
protected syncMutedState(): void {
|
|
474
|
+
if (this.video) {
|
|
475
|
+
this.video.muted = this._muted;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
protected syncControlsState(): void {
|
|
480
|
+
if (this.video) {
|
|
481
|
+
this.video.controls = this._controls;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
protected syncPosterState(): void {
|
|
486
|
+
if (this._lazy && !this.video) {
|
|
487
|
+
this.showPoster();
|
|
488
|
+
} else if (this.video) {
|
|
489
|
+
this.video.poster = this._poster;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
492
493
|
private setupVideoEvents() {
|
|
493
494
|
if (!this.video) return;
|
|
494
495
|
|
|
@@ -556,10 +557,18 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
556
557
|
});
|
|
557
558
|
|
|
558
559
|
this.video.addEventListener("loadedmetadata", () => {
|
|
560
|
+
const width = this.video?.videoWidth || 0;
|
|
561
|
+
const height = this.video?.videoHeight || 0;
|
|
562
|
+
|
|
563
|
+
// Automatically set aspect ratio CSS custom properties
|
|
564
|
+
if (width > 0 && height > 0) {
|
|
565
|
+
this.setAspectRatio(width, height);
|
|
566
|
+
}
|
|
567
|
+
|
|
559
568
|
this.dispatchCustomEvent("loadedmetadata", {
|
|
560
569
|
duration: this.video?.duration || 0,
|
|
561
|
-
videoWidth:
|
|
562
|
-
videoHeight:
|
|
570
|
+
videoWidth: width,
|
|
571
|
+
videoHeight: height,
|
|
563
572
|
});
|
|
564
573
|
});
|
|
565
574
|
}
|
|
@@ -657,7 +666,7 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
657
666
|
}
|
|
658
667
|
|
|
659
668
|
// Public API methods
|
|
660
|
-
public async play(): Promise<void> {
|
|
669
|
+
public override async play(): Promise<void> {
|
|
661
670
|
if (this._lazy && !this.video) {
|
|
662
671
|
this.log("Lazy video needs to be loaded first. Initializing...");
|
|
663
672
|
this.setAttribute("data-should-autoplay", "true");
|
|
@@ -681,7 +690,7 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
681
690
|
}
|
|
682
691
|
}
|
|
683
692
|
|
|
684
|
-
public pause(): void {
|
|
693
|
+
public override pause(): void {
|
|
685
694
|
if (this.video) {
|
|
686
695
|
this.video.pause();
|
|
687
696
|
this.log("Video paused.");
|