@smartimpact-it/modern-video-embed 2.0.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/README.md +1205 -0
- package/dist/components/VimeoEmbed.d.ts +143 -0
- package/dist/components/VimeoEmbed.d.ts.map +1 -0
- package/dist/components/VimeoEmbed.js +1176 -0
- package/dist/components/VimeoEmbed.js.map +1 -0
- package/dist/components/VimeoEmbed.min.js +1 -0
- package/dist/components/YouTubeEmbed.d.ts +225 -0
- package/dist/components/YouTubeEmbed.d.ts.map +1 -0
- package/dist/components/YouTubeEmbed.js +1354 -0
- package/dist/components/YouTubeEmbed.js.map +1 -0
- package/dist/components/YouTubeEmbed.min.js +1 -0
- package/dist/css/components.css +349 -0
- package/dist/css/components.css.map +1 -0
- package/dist/css/components.min.css +1 -0
- package/dist/css/main.css +12210 -0
- package/dist/css/main.css.map +1 -0
- package/dist/css/main.min.css +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/vimeo-only.d.ts +7 -0
- package/dist/vimeo-only.d.ts.map +1 -0
- package/dist/vimeo-only.js +8 -0
- package/dist/vimeo-only.js.map +1 -0
- package/dist/vimeo-only.min.js +1 -0
- package/dist/youtube-only.d.ts +7 -0
- package/dist/youtube-only.d.ts.map +1 -0
- package/dist/youtube-only.js +8 -0
- package/dist/youtube-only.js.map +1 -0
- package/dist/youtube-only.min.js +1 -0
- package/package.json +75 -0
- package/src/components/VimeoEmbed.ts +1340 -0
- package/src/components/YouTubeEmbed.ts +1568 -0
- package/src/index.ts +3 -0
- package/src/styles/README.md +56 -0
- package/src/styles/components.scss +7 -0
- package/src/styles/main.scss +10 -0
- package/src/styles/vimeo-embed.scss +255 -0
- package/src/styles/youtube-embed.scss +261 -0
- package/src/types/common.d.ts +198 -0
- package/src/types/index.ts +7 -0
- package/src/types/vimeo-embed.d.ts +80 -0
- package/src/types/youtube-embed.d.ts +83 -0
- package/src/vimeo-only.ts +9 -0
- package/src/youtube-only.ts +9 -0
|
@@ -0,0 +1,1354 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
11
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
14
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
15
|
+
};
|
|
16
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
17
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
18
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
19
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
20
|
+
};
|
|
21
|
+
var _YouTubeEmbed_instances, _YouTubeEmbed_url, _YouTubeEmbed_videoId, _YouTubeEmbed_autoplay, _YouTubeEmbed_muted, _YouTubeEmbed_controls, _YouTubeEmbed_lazy, _YouTubeEmbed_poster, _YouTubeEmbed_playing, _YouTubeEmbed_background, _YouTubeEmbed_playerVars, _YouTubeEmbed_reflectBooleanAttribute, _YouTubeEmbed_updateBackgroundMode;
|
|
22
|
+
export class YouTubeEmbed extends HTMLElement {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
_YouTubeEmbed_instances.add(this);
|
|
26
|
+
this.iframe = null;
|
|
27
|
+
this.player = null;
|
|
28
|
+
this.playerReady = false;
|
|
29
|
+
this.playPauseButton = null;
|
|
30
|
+
this.initialized = false;
|
|
31
|
+
this.setCustomControlState = null;
|
|
32
|
+
this.updatingAttribute = false; // Flag to prevent recursive attribute updates
|
|
33
|
+
this.updatingMutedState = false; // Flag to prevent sync from overwriting programmatic mute changes
|
|
34
|
+
/** The full YouTube URL (e.g., "https://www.youtube.com/watch?v=...") */
|
|
35
|
+
_YouTubeEmbed_url.set(this, "");
|
|
36
|
+
/** The 11-character YouTube video ID. */
|
|
37
|
+
_YouTubeEmbed_videoId.set(this, "");
|
|
38
|
+
/** Whether the video should autoplay when loaded. Requires `muted` to be true. */
|
|
39
|
+
_YouTubeEmbed_autoplay.set(this, false);
|
|
40
|
+
/** Whether the video audio is muted. */
|
|
41
|
+
_YouTubeEmbed_muted.set(this, false);
|
|
42
|
+
/** Whether to show the native YouTube player controls. */
|
|
43
|
+
_YouTubeEmbed_controls.set(this, false);
|
|
44
|
+
/** Whether to lazy-load the video, showing a poster image until interaction. */
|
|
45
|
+
_YouTubeEmbed_lazy.set(this, false);
|
|
46
|
+
/** URL for a custom poster image. Defaults to YouTube's thumbnail. */
|
|
47
|
+
_YouTubeEmbed_poster.set(this, "");
|
|
48
|
+
/** Read-only property to check if the video is currently playing. */
|
|
49
|
+
_YouTubeEmbed_playing.set(this, false);
|
|
50
|
+
/** Whether the video should act as a background, covering its container. */
|
|
51
|
+
_YouTubeEmbed_background.set(this, false);
|
|
52
|
+
/** Extra parameters to pass to the YouTube player. */
|
|
53
|
+
_YouTubeEmbed_playerVars.set(this, {});
|
|
54
|
+
// Declare the event listener properties
|
|
55
|
+
this.posterClickHandler = null;
|
|
56
|
+
this.keyboardHandler = null;
|
|
57
|
+
this.ariaLiveRegion = null;
|
|
58
|
+
this.apiLoadRetries = 0;
|
|
59
|
+
this.intersectionObserver = null;
|
|
60
|
+
this.hasLoadedVideo = false;
|
|
61
|
+
}
|
|
62
|
+
static get observedAttributes() {
|
|
63
|
+
return [
|
|
64
|
+
"url",
|
|
65
|
+
"video-id",
|
|
66
|
+
"autoplay",
|
|
67
|
+
"controls",
|
|
68
|
+
"lazy",
|
|
69
|
+
"muted",
|
|
70
|
+
"poster",
|
|
71
|
+
"background",
|
|
72
|
+
"player-vars",
|
|
73
|
+
"quality",
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
77
|
+
// Skip processing if we're currently updating an attribute to prevent infinite loops
|
|
78
|
+
if (this.updatingAttribute) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.log(`YouTubeEmbed: Attribute changed - ${name}: ${oldValue} -> ${newValue}`);
|
|
82
|
+
switch (name) {
|
|
83
|
+
case "url":
|
|
84
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_url, newValue || "", "f");
|
|
85
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, this.extractVideoId(__classPrivateFieldGet(this, _YouTubeEmbed_url, "f")), "f");
|
|
86
|
+
if (this.initialized) {
|
|
87
|
+
this.reinitializePlayer();
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case "video-id":
|
|
91
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, newValue || "", "f");
|
|
92
|
+
if (this.initialized) {
|
|
93
|
+
this.reinitializePlayer();
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "autoplay":
|
|
97
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_autoplay, newValue !== null, "f");
|
|
98
|
+
if (!__classPrivateFieldGet(this, _YouTubeEmbed_playing, "f") &&
|
|
99
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_autoplay, "f") &&
|
|
100
|
+
!__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") &&
|
|
101
|
+
this.initialized) {
|
|
102
|
+
this.play();
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "controls":
|
|
106
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_controls, newValue !== null, "f");
|
|
107
|
+
if (this.initialized) {
|
|
108
|
+
this.reinitializePlayer();
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "lazy":
|
|
112
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_lazy, newValue !== null, "f");
|
|
113
|
+
break;
|
|
114
|
+
case "muted":
|
|
115
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, newValue !== null, "f");
|
|
116
|
+
if (this.player && this.playerReady) {
|
|
117
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
118
|
+
this.player.mute();
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.player.unMute();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case "poster":
|
|
126
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_poster, newValue || "", "f");
|
|
127
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") && !this.player) {
|
|
128
|
+
this.showPoster(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case "background":
|
|
132
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_background, newValue !== null, "f");
|
|
133
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_updateBackgroundMode).call(this);
|
|
134
|
+
break;
|
|
135
|
+
case "player-vars":
|
|
136
|
+
try {
|
|
137
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playerVars, newValue ? JSON.parse(newValue) : {}, "f");
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
this.error("YouTubeEmbed: Invalid player-vars JSON:", e);
|
|
141
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playerVars, {}, "f");
|
|
142
|
+
}
|
|
143
|
+
if (this.initialized) {
|
|
144
|
+
this.reinitializePlayer();
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case "quality":
|
|
148
|
+
if (newValue && this.player && this.playerReady) {
|
|
149
|
+
this.setQuality(newValue);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Getters and setters for attributes
|
|
155
|
+
/**
|
|
156
|
+
* Gets or sets the full YouTube URL.
|
|
157
|
+
* When set, it automatically extracts the video ID and reinitializes the player.
|
|
158
|
+
*/
|
|
159
|
+
get url() {
|
|
160
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_url, "f");
|
|
161
|
+
}
|
|
162
|
+
set url(value) {
|
|
163
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_url, value, "f");
|
|
164
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, this.extractVideoId(value), "f");
|
|
165
|
+
if (this.initialized) {
|
|
166
|
+
this.reinitializePlayer();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Gets or sets the 11-character YouTube video ID.
|
|
171
|
+
* Reinitializes the player when set.
|
|
172
|
+
*/
|
|
173
|
+
get videoId() {
|
|
174
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f");
|
|
175
|
+
}
|
|
176
|
+
set videoId(value) {
|
|
177
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, value, "f");
|
|
178
|
+
if (this.initialized) {
|
|
179
|
+
this.reinitializePlayer();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Gets or sets the autoplay state.
|
|
184
|
+
* Note: Autoplay is subject to browser policies and usually requires the video to be muted.
|
|
185
|
+
*/
|
|
186
|
+
get autoplay() {
|
|
187
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_autoplay, "f");
|
|
188
|
+
}
|
|
189
|
+
set autoplay(value) {
|
|
190
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_autoplay, value, "f");
|
|
191
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_reflectBooleanAttribute).call(this, "autoplay", value);
|
|
192
|
+
if (!__classPrivateFieldGet(this, _YouTubeEmbed_playing, "f") && value && !__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") && this.initialized) {
|
|
193
|
+
this.play();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Gets or sets whether native YouTube player controls are visible.
|
|
198
|
+
*/
|
|
199
|
+
get controls() {
|
|
200
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_controls, "f");
|
|
201
|
+
}
|
|
202
|
+
set controls(value) {
|
|
203
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_controls, value, "f");
|
|
204
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_reflectBooleanAttribute).call(this, "controls", value);
|
|
205
|
+
if (this.initialized) {
|
|
206
|
+
this.reinitializePlayer();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Gets or sets the lazy-loading behavior.
|
|
211
|
+
* If true, a poster is shown, and the video loads only on user interaction.
|
|
212
|
+
*/
|
|
213
|
+
get lazy() {
|
|
214
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f");
|
|
215
|
+
}
|
|
216
|
+
set lazy(value) {
|
|
217
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_lazy, value, "f");
|
|
218
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_reflectBooleanAttribute).call(this, "lazy", value);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Gets or sets the muted state of the video.
|
|
222
|
+
*/
|
|
223
|
+
get muted() {
|
|
224
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_muted, "f");
|
|
225
|
+
}
|
|
226
|
+
set muted(value) {
|
|
227
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, value, "f");
|
|
228
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_reflectBooleanAttribute).call(this, "muted", value);
|
|
229
|
+
if (this.player && this.playerReady) {
|
|
230
|
+
this.updatingMutedState = true;
|
|
231
|
+
if (value) {
|
|
232
|
+
this.player.mute();
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.player.unMute();
|
|
236
|
+
}
|
|
237
|
+
// Reset flag after a short delay to allow YouTube API to process
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
this.updatingMutedState = false;
|
|
240
|
+
}, 100);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Gets or sets the URL for a custom poster image.
|
|
245
|
+
* If not set, a default YouTube thumbnail is used for lazy-loaded videos.
|
|
246
|
+
*/
|
|
247
|
+
get poster() {
|
|
248
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_poster, "f");
|
|
249
|
+
}
|
|
250
|
+
set poster(value) {
|
|
251
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_poster, value, "f");
|
|
252
|
+
this.updatingAttribute = true;
|
|
253
|
+
if (value) {
|
|
254
|
+
this.setAttribute("poster", value);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
this.removeAttribute("poster");
|
|
258
|
+
}
|
|
259
|
+
this.updatingAttribute = false;
|
|
260
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") && !this.player) {
|
|
261
|
+
this.showPoster(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Returns `true` if the video is currently playing.
|
|
266
|
+
*/
|
|
267
|
+
get playing() {
|
|
268
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_playing, "f");
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Gets or sets the background mode.
|
|
272
|
+
* In background mode, the video covers its container, is muted, and has no controls.
|
|
273
|
+
*/
|
|
274
|
+
get background() {
|
|
275
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_background, "f");
|
|
276
|
+
}
|
|
277
|
+
set background(value) {
|
|
278
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_background, value, "f");
|
|
279
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_reflectBooleanAttribute).call(this, "background", value);
|
|
280
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_updateBackgroundMode).call(this);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Gets or sets extra player parameters.
|
|
284
|
+
* @param {Record<string, string | number | boolean>} vars - An object of key-value pairs to pass to the player.
|
|
285
|
+
*/
|
|
286
|
+
get playerVars() {
|
|
287
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_playerVars, "f");
|
|
288
|
+
}
|
|
289
|
+
set playerVars(vars) {
|
|
290
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playerVars, vars, "f");
|
|
291
|
+
this.setAttribute("player-vars", JSON.stringify(vars));
|
|
292
|
+
if (this.initialized) {
|
|
293
|
+
this.reinitializePlayer();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
extractVideoId(url) {
|
|
297
|
+
if (!url)
|
|
298
|
+
return "";
|
|
299
|
+
// Comprehensive regex to handle various YouTube URL formats
|
|
300
|
+
const regex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/;
|
|
301
|
+
const match = url.match(regex);
|
|
302
|
+
if (match && match[1]) {
|
|
303
|
+
return match[1];
|
|
304
|
+
}
|
|
305
|
+
// If it's already just an ID
|
|
306
|
+
if (/^[a-zA-Z0-9_-]{11}$/.test(url)) {
|
|
307
|
+
return url;
|
|
308
|
+
}
|
|
309
|
+
this.warn(`YouTubeEmbed: Could not extract video ID from "${url}"`);
|
|
310
|
+
return "";
|
|
311
|
+
}
|
|
312
|
+
log(...args) {
|
|
313
|
+
if (YouTubeEmbed.DEBUG) {
|
|
314
|
+
console.log("YouTubeEmbed:", ...args);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
warn(...args) {
|
|
318
|
+
console.warn(...args);
|
|
319
|
+
}
|
|
320
|
+
error(...args) {
|
|
321
|
+
console.error(...args);
|
|
322
|
+
}
|
|
323
|
+
dispatchCustomEvent(eventName, detail) {
|
|
324
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
325
|
+
detail,
|
|
326
|
+
bubbles: true,
|
|
327
|
+
composed: true,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
isValidPosterUrl(url) {
|
|
331
|
+
if (!url)
|
|
332
|
+
return false;
|
|
333
|
+
try {
|
|
334
|
+
new URL(url);
|
|
335
|
+
// Exclude known problematic domains
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
catch (_a) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Setup fullscreen change event listener
|
|
344
|
+
*/
|
|
345
|
+
setupFullscreenListener() {
|
|
346
|
+
const handleFullscreenChange = () => {
|
|
347
|
+
const isFullscreen = this.isFullscreen();
|
|
348
|
+
this.dispatchCustomEvent("fullscreenchange", { isFullscreen });
|
|
349
|
+
if (isFullscreen) {
|
|
350
|
+
this.classList.add("is-fullscreen");
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
this.classList.remove("is-fullscreen");
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
357
|
+
document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
|
|
358
|
+
document.addEventListener("mozfullscreenchange", handleFullscreenChange);
|
|
359
|
+
document.addEventListener("MSFullscreenChange", handleFullscreenChange);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Setup keyboard event handlers for accessibility
|
|
363
|
+
*/
|
|
364
|
+
setupKeyboardHandlers() {
|
|
365
|
+
this.keyboardHandler = (e) => {
|
|
366
|
+
var _a, _b;
|
|
367
|
+
// Only handle if player is ready and not in input element
|
|
368
|
+
if (!this.playerReady || ((_a = e.target) === null || _a === void 0 ? void 0 : _a.tagName) === "INPUT") {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
switch (e.key.toLowerCase()) {
|
|
372
|
+
case "k":
|
|
373
|
+
case " ":
|
|
374
|
+
// Play/Pause
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
this.togglePlay();
|
|
377
|
+
this.announceToScreenReader(__classPrivateFieldGet(this, _YouTubeEmbed_playing, "f") ? "Video playing" : "Video paused");
|
|
378
|
+
break;
|
|
379
|
+
case "m":
|
|
380
|
+
// Mute/Unmute
|
|
381
|
+
e.preventDefault();
|
|
382
|
+
this.toggleMute();
|
|
383
|
+
(_b = this.player) === null || _b === void 0 ? void 0 : _b.isMuted().then((muted) => {
|
|
384
|
+
this.announceToScreenReader(muted ? "Video muted" : "Video unmuted");
|
|
385
|
+
});
|
|
386
|
+
break;
|
|
387
|
+
case "f":
|
|
388
|
+
// Fullscreen toggle
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
if (document.fullscreenElement) {
|
|
391
|
+
document.exitFullscreen();
|
|
392
|
+
this.announceToScreenReader("Exited fullscreen");
|
|
393
|
+
}
|
|
394
|
+
else if (this.enterFullscreen) {
|
|
395
|
+
this.enterFullscreen();
|
|
396
|
+
this.announceToScreenReader("Entered fullscreen");
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
this.addEventListener("keydown", this.keyboardHandler);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Announce message to screen readers
|
|
405
|
+
*/
|
|
406
|
+
announceToScreenReader(message) {
|
|
407
|
+
if (this.ariaLiveRegion) {
|
|
408
|
+
this.ariaLiveRegion.textContent = message;
|
|
409
|
+
// Clear after a brief delay
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
if (this.ariaLiveRegion) {
|
|
412
|
+
this.ariaLiveRegion.textContent = "";
|
|
413
|
+
}
|
|
414
|
+
}, 1000);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Add preconnect and dns-prefetch hints for YouTube domains
|
|
419
|
+
*/
|
|
420
|
+
addResourceHints() {
|
|
421
|
+
const hints = [
|
|
422
|
+
{ rel: "preconnect", href: "https://www.youtube.com" },
|
|
423
|
+
{ rel: "preconnect", href: "https://i.ytimg.com" },
|
|
424
|
+
{ rel: "dns-prefetch", href: "https://www.youtube.com" },
|
|
425
|
+
{ rel: "dns-prefetch", href: "https://i.ytimg.com" },
|
|
426
|
+
];
|
|
427
|
+
hints.forEach(({ rel, href }) => {
|
|
428
|
+
if (!document.querySelector(`link[rel="${rel}"][href="${href}"]`)) {
|
|
429
|
+
const link = document.createElement("link");
|
|
430
|
+
link.rel = rel;
|
|
431
|
+
link.href = href;
|
|
432
|
+
if (rel === "preconnect") {
|
|
433
|
+
link.crossOrigin = "anonymous";
|
|
434
|
+
}
|
|
435
|
+
document.head.appendChild(link);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Setup Intersection Observer for lazy loading
|
|
441
|
+
*/
|
|
442
|
+
setupIntersectionObserver() {
|
|
443
|
+
if (!("IntersectionObserver" in window)) {
|
|
444
|
+
// Fallback: load immediately if IntersectionObserver not supported
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const options = {
|
|
448
|
+
root: null,
|
|
449
|
+
rootMargin: "50px", // Start loading 50px before entering viewport
|
|
450
|
+
threshold: 0.01,
|
|
451
|
+
};
|
|
452
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
453
|
+
entries.forEach((entry) => {
|
|
454
|
+
if (entry.isIntersecting && !this.hasLoadedVideo) {
|
|
455
|
+
this.hasLoadedVideo = true;
|
|
456
|
+
this.log("YouTubeEmbed: Video entering viewport, loading...");
|
|
457
|
+
// Check if poster was clicked before intersection
|
|
458
|
+
const shouldAutoplay = this.getAttribute("data-poster-autoplay") === "true";
|
|
459
|
+
if (!shouldAutoplay) {
|
|
460
|
+
// Just load the player, don't autoplay
|
|
461
|
+
this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
462
|
+
}
|
|
463
|
+
// Disconnect observer after loading
|
|
464
|
+
if (this.intersectionObserver) {
|
|
465
|
+
this.intersectionObserver.disconnect();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}, options);
|
|
470
|
+
this.intersectionObserver.observe(this);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Display error message to user with retry option
|
|
474
|
+
*/
|
|
475
|
+
showErrorMessage(message) {
|
|
476
|
+
const errorDiv = document.createElement("div");
|
|
477
|
+
errorDiv.className = "youtube-error-message";
|
|
478
|
+
errorDiv.setAttribute("role", "alert");
|
|
479
|
+
errorDiv.innerHTML = `
|
|
480
|
+
<div class="error-content">
|
|
481
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
482
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
483
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
484
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
485
|
+
</svg>
|
|
486
|
+
<p class="error-message">${message}</p>
|
|
487
|
+
<button class="retry-button">Retry</button>
|
|
488
|
+
</div>
|
|
489
|
+
`;
|
|
490
|
+
const retryButton = errorDiv.querySelector(".retry-button");
|
|
491
|
+
if (retryButton) {
|
|
492
|
+
retryButton.addEventListener("click", () => {
|
|
493
|
+
this.apiLoadRetries = 0;
|
|
494
|
+
errorDiv.remove();
|
|
495
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f")) {
|
|
496
|
+
this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
// Clear existing content
|
|
501
|
+
this.innerHTML = "";
|
|
502
|
+
this.appendChild(errorDiv);
|
|
503
|
+
}
|
|
504
|
+
connectedCallback() {
|
|
505
|
+
YouTubeEmbed.instanceCount++;
|
|
506
|
+
this.log("YouTubeEmbed: connectedCallback called, instance count:", YouTubeEmbed.instanceCount);
|
|
507
|
+
// Add resource hints on first instance
|
|
508
|
+
if (YouTubeEmbed.instanceCount === 1) {
|
|
509
|
+
this.addResourceHints();
|
|
510
|
+
}
|
|
511
|
+
// Initialize attributes from HTML
|
|
512
|
+
const urlAttr = this.getAttribute("url");
|
|
513
|
+
const videoIdAttr = this.getAttribute("video-id");
|
|
514
|
+
if (urlAttr) {
|
|
515
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_url, urlAttr, "f");
|
|
516
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, this.extractVideoId(urlAttr), "f");
|
|
517
|
+
}
|
|
518
|
+
else if (videoIdAttr) {
|
|
519
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, videoIdAttr, "f");
|
|
520
|
+
}
|
|
521
|
+
// Set boolean attributes
|
|
522
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_autoplay, this.hasAttribute("autoplay"), "f");
|
|
523
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_controls, this.hasAttribute("controls"), "f");
|
|
524
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_lazy, this.hasAttribute("lazy"), "f");
|
|
525
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, this.hasAttribute("muted"), "f");
|
|
526
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_background, this.hasAttribute("background"), "f");
|
|
527
|
+
// Set string attributes
|
|
528
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_poster, this.getAttribute("poster") || "", "f");
|
|
529
|
+
try {
|
|
530
|
+
const playerVarsAttr = this.getAttribute("player-vars");
|
|
531
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playerVars, playerVarsAttr ? JSON.parse(playerVarsAttr) : {}, "f");
|
|
532
|
+
}
|
|
533
|
+
catch (e) {
|
|
534
|
+
this.error("YouTubeEmbed: Invalid player-vars JSON on init:", e);
|
|
535
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playerVars, {}, "f");
|
|
536
|
+
}
|
|
537
|
+
if (!__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f")) {
|
|
538
|
+
this.error("YouTubeEmbed: Missing or invalid video ID");
|
|
539
|
+
this.dispatchCustomEvent("error", {
|
|
540
|
+
message: "Missing or invalid video ID",
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
// Use the element itself as the container
|
|
545
|
+
this.classList.add("youtube-embed-wrapper");
|
|
546
|
+
// Add ARIA role and tabindex for accessibility
|
|
547
|
+
this.setAttribute("role", "region");
|
|
548
|
+
this.setAttribute("aria-label", "Video player");
|
|
549
|
+
if (!this.hasAttribute("tabindex")) {
|
|
550
|
+
this.setAttribute("tabindex", "0");
|
|
551
|
+
}
|
|
552
|
+
// Create ARIA live region for screen reader announcements
|
|
553
|
+
this.ariaLiveRegion = document.createElement("div");
|
|
554
|
+
this.ariaLiveRegion.setAttribute("aria-live", "polite");
|
|
555
|
+
this.ariaLiveRegion.setAttribute("aria-atomic", "true");
|
|
556
|
+
this.ariaLiveRegion.className = "sr-only";
|
|
557
|
+
this.ariaLiveRegion.style.cssText =
|
|
558
|
+
"position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden;";
|
|
559
|
+
this.appendChild(this.ariaLiveRegion);
|
|
560
|
+
// Setup keyboard event handlers
|
|
561
|
+
this.setupKeyboardHandlers();
|
|
562
|
+
// Setup fullscreen change listener
|
|
563
|
+
this.setupFullscreenListener();
|
|
564
|
+
__classPrivateFieldGet(this, _YouTubeEmbed_instances, "m", _YouTubeEmbed_updateBackgroundMode).call(this);
|
|
565
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f")) {
|
|
566
|
+
this.showPoster(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
567
|
+
// Setup Intersection Observer for better performance
|
|
568
|
+
this.setupIntersectionObserver();
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// Initialize the player immediately
|
|
572
|
+
this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
573
|
+
}
|
|
574
|
+
this.initialized = true;
|
|
575
|
+
this.dispatchCustomEvent("connected");
|
|
576
|
+
}
|
|
577
|
+
disconnectedCallback() {
|
|
578
|
+
YouTubeEmbed.instanceCount--;
|
|
579
|
+
this.log("YouTubeEmbed: disconnectedCallback called, remaining instances:", YouTubeEmbed.instanceCount);
|
|
580
|
+
// Stop the video and destroy the player instance
|
|
581
|
+
if (this.player) {
|
|
582
|
+
try {
|
|
583
|
+
this.player.destroy();
|
|
584
|
+
this.log("YouTubeEmbed: Player destroyed.");
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
this.warn("YouTubeEmbed: Error destroying player:", error);
|
|
588
|
+
}
|
|
589
|
+
this.player = null;
|
|
590
|
+
}
|
|
591
|
+
// Remove keyboard event handlers
|
|
592
|
+
if (this.keyboardHandler) {
|
|
593
|
+
this.removeEventListener("keydown", this.keyboardHandler);
|
|
594
|
+
this.keyboardHandler = null;
|
|
595
|
+
}
|
|
596
|
+
// Remove event listeners from poster and play button
|
|
597
|
+
const poster = this.querySelector(".youtube-poster");
|
|
598
|
+
const buttonOverlay = this.querySelector(".button-overlay");
|
|
599
|
+
if (poster && this.posterClickHandler) {
|
|
600
|
+
poster.removeEventListener("click", this.posterClickHandler);
|
|
601
|
+
this.posterClickHandler = null;
|
|
602
|
+
}
|
|
603
|
+
if (buttonOverlay && this.posterClickHandler) {
|
|
604
|
+
buttonOverlay.removeEventListener("click", this.posterClickHandler);
|
|
605
|
+
}
|
|
606
|
+
// Clear element content
|
|
607
|
+
this.innerHTML = "";
|
|
608
|
+
this.classList.remove("youtube-embed-wrapper", "youtube-embed-container", "is-playing");
|
|
609
|
+
// Only clean up global resources if this is the last instance
|
|
610
|
+
if (YouTubeEmbed.instanceCount === 0) {
|
|
611
|
+
this.log("YouTubeEmbed: Last instance, cleaning up global resources");
|
|
612
|
+
// Remove the YouTube API script
|
|
613
|
+
const script = document.querySelector('script[src="https://www.youtube.com/iframe_api"]');
|
|
614
|
+
if (script) {
|
|
615
|
+
script.remove();
|
|
616
|
+
this.log("YouTubeEmbed: YouTube API script removed.");
|
|
617
|
+
}
|
|
618
|
+
// Clear global references
|
|
619
|
+
if (window.onYouTubeIframeAPIReady) {
|
|
620
|
+
delete window.onYouTubeIframeAPIReady;
|
|
621
|
+
}
|
|
622
|
+
// Reset static flags
|
|
623
|
+
YouTubeEmbed.apiLoaded = false;
|
|
624
|
+
YouTubeEmbed.apiReady = false;
|
|
625
|
+
}
|
|
626
|
+
// Nullify DOM references
|
|
627
|
+
this.iframe = null;
|
|
628
|
+
this.playPauseButton = null;
|
|
629
|
+
this.setCustomControlState = null;
|
|
630
|
+
// Dispatch disconnected event
|
|
631
|
+
this.dispatchCustomEvent("disconnected");
|
|
632
|
+
this.log("YouTubeEmbed: Cleanup complete.");
|
|
633
|
+
}
|
|
634
|
+
reinitializePlayer() {
|
|
635
|
+
if (!this.initialized || !__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f")) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
// Remove existing player
|
|
639
|
+
if (this.player) {
|
|
640
|
+
try {
|
|
641
|
+
this.player.destroy();
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
this.warn("YouTubeEmbed: Error destroying player:", error);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Clear and reinitialize
|
|
648
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f")) {
|
|
649
|
+
this.showPoster(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
showPoster(videoId) {
|
|
656
|
+
// Use custom poster if valid, otherwise use YouTube thumbnail
|
|
657
|
+
const thumbnailUrl = this.isValidPosterUrl(__classPrivateFieldGet(this, _YouTubeEmbed_poster, "f"))
|
|
658
|
+
? __classPrivateFieldGet(this, _YouTubeEmbed_poster, "f")
|
|
659
|
+
: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
|
|
660
|
+
this.log("YouTubeEmbed: Using poster URL:", thumbnailUrl);
|
|
661
|
+
// Clear any existing content and event listeners
|
|
662
|
+
this.innerHTML = "";
|
|
663
|
+
// Clear old event listeners if they exist
|
|
664
|
+
if (this.posterClickHandler) {
|
|
665
|
+
this.posterClickHandler = null;
|
|
666
|
+
}
|
|
667
|
+
// Create poster image
|
|
668
|
+
const poster = document.createElement("img");
|
|
669
|
+
poster.src = thumbnailUrl;
|
|
670
|
+
poster.alt = "YouTube Video Thumbnail";
|
|
671
|
+
poster.classList.add("youtube-poster", "video-poster"); // Add both classes for compatibility
|
|
672
|
+
poster.loading = "lazy"; // Optimize image loading
|
|
673
|
+
// Add error handling for poster loading
|
|
674
|
+
poster.onerror = () => {
|
|
675
|
+
this.warn("YouTubeEmbed: Poster failed to load, using white background.");
|
|
676
|
+
poster.style.display = "none";
|
|
677
|
+
this.style.backgroundColor = "#FFFFFF";
|
|
678
|
+
};
|
|
679
|
+
// Create button overlay with play button
|
|
680
|
+
const buttonOverlay = document.createElement("div");
|
|
681
|
+
buttonOverlay.classList.add("button-overlay");
|
|
682
|
+
buttonOverlay.setAttribute("role", "button");
|
|
683
|
+
buttonOverlay.setAttribute("tabindex", "0");
|
|
684
|
+
buttonOverlay.setAttribute("aria-label", "Play video");
|
|
685
|
+
const button = document.createElement("div");
|
|
686
|
+
button.classList.add("button");
|
|
687
|
+
button.setAttribute("aria-hidden", "true");
|
|
688
|
+
buttonOverlay.appendChild(button);
|
|
689
|
+
// Handle click to load video - store reference for cleanup
|
|
690
|
+
const loadVideo = () => {
|
|
691
|
+
this.log("YouTubeEmbed: Loading video from poster click");
|
|
692
|
+
this.setAttribute("data-poster-autoplay", "true");
|
|
693
|
+
try {
|
|
694
|
+
this.initializePlayer(videoId);
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
this.error("YouTubeEmbed: Error initializing player from poster:", error);
|
|
698
|
+
this.dispatchCustomEvent("error", { message: "Failed to load video" });
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
// Store reference for cleanup
|
|
702
|
+
this.posterClickHandler = loadVideo;
|
|
703
|
+
// Add keyboard support
|
|
704
|
+
const keyboardActivate = (e) => {
|
|
705
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
706
|
+
e.preventDefault();
|
|
707
|
+
loadVideo(e);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
poster.addEventListener("click", loadVideo);
|
|
711
|
+
buttonOverlay.addEventListener("click", loadVideo);
|
|
712
|
+
buttonOverlay.addEventListener("keydown", keyboardActivate);
|
|
713
|
+
this.appendChild(poster);
|
|
714
|
+
this.appendChild(buttonOverlay);
|
|
715
|
+
this.log("YouTubeEmbed: Poster displayed for lazy loading");
|
|
716
|
+
}
|
|
717
|
+
initializePlayer(videoId) {
|
|
718
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
719
|
+
this.log("YouTubeEmbed: Initializing player for video ID:", videoId);
|
|
720
|
+
const isBackground = __classPrivateFieldGet(this, _YouTubeEmbed_background, "f");
|
|
721
|
+
// Background videos must autoplay and be muted, with no controls.
|
|
722
|
+
const autoplay = isBackground ||
|
|
723
|
+
this.hasAttribute("autoplay") ||
|
|
724
|
+
this.hasAttribute("data-poster-autoplay") ||
|
|
725
|
+
this.hasAttribute("data-should-autoplay");
|
|
726
|
+
const controls = isBackground ? false : this.hasAttribute("controls");
|
|
727
|
+
const mute = isBackground || autoplay; // Mute if background or if autoplaying
|
|
728
|
+
// Remove the temporary autoplay flags
|
|
729
|
+
if (this.hasAttribute("data-poster-autoplay")) {
|
|
730
|
+
this.removeAttribute("data-poster-autoplay");
|
|
731
|
+
}
|
|
732
|
+
if (this.hasAttribute("data-should-autoplay")) {
|
|
733
|
+
this.removeAttribute("data-should-autoplay");
|
|
734
|
+
}
|
|
735
|
+
this.log("YouTubeEmbed: Creating iframe with autoplay:", autoplay, "controls:", controls);
|
|
736
|
+
this.iframe = document.createElement("iframe");
|
|
737
|
+
// Build URL parameters to handle YouTube's strict policies
|
|
738
|
+
const params = new URLSearchParams(Object.assign({ enablejsapi: "1", autoplay: autoplay ? "1" : "0", controls: !controls && !isBackground ? "1" : "0", mute: mute ? "1" : "0", playsinline: "1", rel: "0", modestbranding: "1", origin: window.location.origin || "localhost" }, __classPrivateFieldGet(this, _YouTubeEmbed_playerVars, "f")));
|
|
739
|
+
// Add muted state if explicitly set
|
|
740
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
741
|
+
params.set("mute", "1");
|
|
742
|
+
}
|
|
743
|
+
// Update internal muted state if autoplay forces muting
|
|
744
|
+
if (autoplay && !__classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
745
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, true, "f");
|
|
746
|
+
this.log("YouTubeEmbed: Autoplay enabled, forcing muted state for compliance");
|
|
747
|
+
}
|
|
748
|
+
this.iframe.src = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
|
|
749
|
+
this.iframe.allow =
|
|
750
|
+
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
|
|
751
|
+
this.iframe.allowFullscreen = true;
|
|
752
|
+
this.iframe.style.position = "absolute";
|
|
753
|
+
this.iframe.style.border = "none";
|
|
754
|
+
// Disable pointer events in background mode to allow clicks to pass through
|
|
755
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_background, "f")) {
|
|
756
|
+
this.iframe.style.pointerEvents = "none";
|
|
757
|
+
// CSS handles the sizing for background videos
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
this.iframe.style.top = "0";
|
|
761
|
+
this.iframe.style.left = "0";
|
|
762
|
+
this.iframe.style.width = "100%";
|
|
763
|
+
this.iframe.style.height = "100%";
|
|
764
|
+
}
|
|
765
|
+
this.log("YouTubeEmbed: Iframe created with src:", this.iframe.src);
|
|
766
|
+
this.replaceChildren(this.iframe);
|
|
767
|
+
if (!controls) {
|
|
768
|
+
this.addCustomControls();
|
|
769
|
+
}
|
|
770
|
+
// Initialize the YouTube API player for advanced control
|
|
771
|
+
try {
|
|
772
|
+
yield YouTubeEmbed.loadYouTubeAPIWithRetry(this.apiLoadRetries);
|
|
773
|
+
this.player = new YT.Player(this.iframe, {
|
|
774
|
+
videoId,
|
|
775
|
+
events: {
|
|
776
|
+
onReady: () => {
|
|
777
|
+
this.log("YouTubeEmbed: Player is ready.");
|
|
778
|
+
this.playerReady = true;
|
|
779
|
+
// Sync muted state with YouTube player
|
|
780
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
781
|
+
this.player.mute();
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
this.player.unMute();
|
|
785
|
+
}
|
|
786
|
+
// Check if autoplay was enabled during initialization and update custom control state
|
|
787
|
+
if (autoplay && this.setCustomControlState) {
|
|
788
|
+
this.setCustomControlState(true);
|
|
789
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playing, true, "f");
|
|
790
|
+
}
|
|
791
|
+
this.dispatchCustomEvent("ready");
|
|
792
|
+
},
|
|
793
|
+
onStateChange: (event) => {
|
|
794
|
+
const isPlaying = event.data === YT.PlayerState.PLAYING;
|
|
795
|
+
const isPaused = event.data === YT.PlayerState.PAUSED;
|
|
796
|
+
const isEnded = event.data === YT.PlayerState.ENDED;
|
|
797
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playing, isPlaying, "f");
|
|
798
|
+
// Sync muted state with actual player state (only if not programmatically updating)
|
|
799
|
+
try {
|
|
800
|
+
if (!this.updatingMutedState &&
|
|
801
|
+
this.player &&
|
|
802
|
+
typeof this.player.isMuted === "function") {
|
|
803
|
+
const actualMuted = this.player.isMuted();
|
|
804
|
+
if (actualMuted !== __classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
805
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, actualMuted, "f");
|
|
806
|
+
this.log("YouTubeEmbed: Synced muted state:", actualMuted);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
this.warn("YouTubeEmbed: Could not sync muted state:", error);
|
|
812
|
+
}
|
|
813
|
+
// Update custom control state based on player state
|
|
814
|
+
if (this.setCustomControlState) {
|
|
815
|
+
this.setCustomControlState(isPlaying);
|
|
816
|
+
}
|
|
817
|
+
// Dispatch appropriate events
|
|
818
|
+
if (isPlaying) {
|
|
819
|
+
this.dispatchCustomEvent("play");
|
|
820
|
+
}
|
|
821
|
+
else if (isPaused) {
|
|
822
|
+
this.dispatchCustomEvent("pause");
|
|
823
|
+
}
|
|
824
|
+
else if (isEnded) {
|
|
825
|
+
this.dispatchCustomEvent("ended");
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
onError: (event) => {
|
|
829
|
+
this.error("YouTubeEmbed: Player encountered an error:", event);
|
|
830
|
+
let errorMessage = "Unknown error occurred";
|
|
831
|
+
// Handle specific YouTube error codes
|
|
832
|
+
switch (event.data) {
|
|
833
|
+
case 2:
|
|
834
|
+
errorMessage = "Invalid video ID";
|
|
835
|
+
break;
|
|
836
|
+
case 5:
|
|
837
|
+
errorMessage = "HTML5 player error";
|
|
838
|
+
break;
|
|
839
|
+
case 100:
|
|
840
|
+
errorMessage = "Video not found or private";
|
|
841
|
+
break;
|
|
842
|
+
case 101:
|
|
843
|
+
case 150:
|
|
844
|
+
errorMessage =
|
|
845
|
+
"Video cannot be embedded (restrictions by owner)";
|
|
846
|
+
break;
|
|
847
|
+
default:
|
|
848
|
+
errorMessage = `YouTube error code: ${event.data}`;
|
|
849
|
+
}
|
|
850
|
+
this.dispatchCustomEvent("error", {
|
|
851
|
+
message: errorMessage,
|
|
852
|
+
code: event.data,
|
|
853
|
+
});
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
this.error("YouTubeEmbed: YouTube API initialization failed:", error);
|
|
860
|
+
this.showErrorMessage(error instanceof Error
|
|
861
|
+
? error.message
|
|
862
|
+
: "Failed to load YouTube player. Please refresh the page or check your connection.");
|
|
863
|
+
this.dispatchCustomEvent("error", {
|
|
864
|
+
message: error instanceof Error ? error.message : "API load failed",
|
|
865
|
+
code: "API_LOAD_ERROR",
|
|
866
|
+
retryable: true,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
static loadYouTubeAPI() {
|
|
872
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
873
|
+
return new Promise((resolve, reject) => {
|
|
874
|
+
if (this.apiReady) {
|
|
875
|
+
resolve();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const timeoutId = setTimeout(() => {
|
|
879
|
+
cleanup();
|
|
880
|
+
reject(new Error("YouTube API loading timeout. The API script may be blocked by an ad blocker or network issue."));
|
|
881
|
+
}, this.API_LOAD_TIMEOUT);
|
|
882
|
+
const cleanup = () => {
|
|
883
|
+
clearTimeout(timeoutId);
|
|
884
|
+
delete window.onYouTubeIframeAPIReady;
|
|
885
|
+
};
|
|
886
|
+
if (!this.apiLoaded) {
|
|
887
|
+
const script = document.createElement("script");
|
|
888
|
+
script.src = "https://www.youtube.com/iframe_api";
|
|
889
|
+
script.onerror = () => {
|
|
890
|
+
cleanup();
|
|
891
|
+
reject(new Error("Failed to load YouTube API script. Please check your network connection or disable ad blockers."));
|
|
892
|
+
};
|
|
893
|
+
document.head.appendChild(script);
|
|
894
|
+
this.apiLoaded = true;
|
|
895
|
+
}
|
|
896
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
897
|
+
this.apiReady = true;
|
|
898
|
+
cleanup();
|
|
899
|
+
resolve();
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Load YouTube API with retry mechanism
|
|
906
|
+
*/
|
|
907
|
+
static loadYouTubeAPIWithRetry() {
|
|
908
|
+
return __awaiter(this, arguments, void 0, function* (retries = 0) {
|
|
909
|
+
try {
|
|
910
|
+
yield this.loadYouTubeAPI();
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
if (retries < this.MAX_API_RETRIES) {
|
|
914
|
+
console.warn(`YouTubeEmbed: API load failed, retrying (${retries + 1}/${this.MAX_API_RETRIES})...`);
|
|
915
|
+
yield new Promise((resolve) => setTimeout(resolve, this.API_RETRY_DELAY));
|
|
916
|
+
return this.loadYouTubeAPIWithRetry(retries + 1);
|
|
917
|
+
}
|
|
918
|
+
throw error;
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
addCustomControls() {
|
|
923
|
+
this.classList.add("youtube-embed-container");
|
|
924
|
+
// Create button overlay
|
|
925
|
+
const buttonOverlay = document.createElement("div");
|
|
926
|
+
buttonOverlay.classList.add("button-overlay");
|
|
927
|
+
this.playPauseButton = document.createElement("div");
|
|
928
|
+
this.playPauseButton.classList.add("button");
|
|
929
|
+
let isPlaying = false;
|
|
930
|
+
const setPlayingState = (playing) => {
|
|
931
|
+
isPlaying = playing;
|
|
932
|
+
if (playing) {
|
|
933
|
+
this.classList.add("is-playing");
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
this.classList.remove("is-playing");
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
const togglePlayPause = () => {
|
|
940
|
+
if (isPlaying) {
|
|
941
|
+
// Pause the video
|
|
942
|
+
if (this.player && typeof this.player.pauseVideo === "function") {
|
|
943
|
+
this.player.pauseVideo();
|
|
944
|
+
}
|
|
945
|
+
else if (this.iframe && this.iframe.contentWindow) {
|
|
946
|
+
this.iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', "*");
|
|
947
|
+
}
|
|
948
|
+
setPlayingState(false);
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
// Play the video
|
|
952
|
+
if (this.player && typeof this.player.playVideo === "function") {
|
|
953
|
+
this.player.playVideo();
|
|
954
|
+
}
|
|
955
|
+
else if (this.iframe && this.iframe.contentWindow) {
|
|
956
|
+
this.iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', "*");
|
|
957
|
+
}
|
|
958
|
+
setPlayingState(true);
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
// Store reference to setPlayingState for external control
|
|
962
|
+
this.setCustomControlState = setPlayingState;
|
|
963
|
+
buttonOverlay.addEventListener("click", togglePlayPause);
|
|
964
|
+
if (this.playPauseButton) {
|
|
965
|
+
buttonOverlay.appendChild(this.playPauseButton);
|
|
966
|
+
}
|
|
967
|
+
this.appendChild(buttonOverlay);
|
|
968
|
+
}
|
|
969
|
+
// Update the playVideo method to wait for the player to be ready
|
|
970
|
+
/**
|
|
971
|
+
* Plays the video. If the video is lazy-loaded and not yet initialized, it will be loaded first.
|
|
972
|
+
*/
|
|
973
|
+
play() {
|
|
974
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
975
|
+
// Check if this is a lazy-loaded video that hasn't been initialized yet
|
|
976
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") && !this.player && !this.iframe) {
|
|
977
|
+
this.log("YouTubeEmbed: Lazy video needs to be loaded first. Initializing...");
|
|
978
|
+
// Set a flag to auto-play after initialization
|
|
979
|
+
this.setAttribute("data-should-autoplay", "true");
|
|
980
|
+
// Initialize the player (this will load the video and replace the poster)
|
|
981
|
+
yield this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
982
|
+
// Wait a bit for the player to be ready after initialization
|
|
983
|
+
yield new Promise((resolve) => setTimeout(resolve, 1000));
|
|
984
|
+
}
|
|
985
|
+
if (!this.playerReady) {
|
|
986
|
+
this.warn("YouTubeEmbed: Player is not ready. Waiting for initialization.");
|
|
987
|
+
yield new Promise((resolve) => {
|
|
988
|
+
const interval = setInterval(() => {
|
|
989
|
+
if (this.playerReady) {
|
|
990
|
+
clearInterval(interval);
|
|
991
|
+
resolve(null);
|
|
992
|
+
}
|
|
993
|
+
}, 100);
|
|
994
|
+
// Timeout after 5 seconds
|
|
995
|
+
setTimeout(() => {
|
|
996
|
+
clearInterval(interval);
|
|
997
|
+
resolve(null);
|
|
998
|
+
}, 5000);
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
// Remove the autoplay flag if it was set
|
|
1002
|
+
this.removeAttribute("data-should-autoplay");
|
|
1003
|
+
if (this.player && typeof this.player.playVideo === "function") {
|
|
1004
|
+
this.player.playVideo();
|
|
1005
|
+
this.log("YouTubeEmbed: Video playback started via API.");
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
this.log("YouTubeEmbed: Using iframe postMessage for play.");
|
|
1009
|
+
// Fallback: try to use postMessage to control iframe
|
|
1010
|
+
if (this.iframe && this.iframe.contentWindow) {
|
|
1011
|
+
this.iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', "*");
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playing, true, "f");
|
|
1015
|
+
// Update custom control state
|
|
1016
|
+
if (this.setCustomControlState) {
|
|
1017
|
+
this.setCustomControlState(true);
|
|
1018
|
+
}
|
|
1019
|
+
this.dispatchCustomEvent("play");
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Pauses the currently playing video.
|
|
1024
|
+
*/
|
|
1025
|
+
pause() {
|
|
1026
|
+
if (this.player && typeof this.player.pauseVideo === "function") {
|
|
1027
|
+
this.player.pauseVideo();
|
|
1028
|
+
this.log("YouTubeEmbed: Video paused via API.");
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
this.log("YouTubeEmbed: Using iframe postMessage for pause.");
|
|
1032
|
+
// Fallback: try to use postMessage to control iframe
|
|
1033
|
+
if (this.iframe && this.iframe.contentWindow) {
|
|
1034
|
+
this.iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', "*");
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playing, false, "f");
|
|
1038
|
+
// Update custom control state
|
|
1039
|
+
if (this.setCustomControlState) {
|
|
1040
|
+
this.setCustomControlState(false);
|
|
1041
|
+
}
|
|
1042
|
+
this.dispatchCustomEvent("pause");
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Stops the video and resets it to the beginning.
|
|
1046
|
+
*/
|
|
1047
|
+
stopVideo() {
|
|
1048
|
+
if (this.player && typeof this.player.stopVideo === "function") {
|
|
1049
|
+
this.player.stopVideo();
|
|
1050
|
+
this.log("YouTubeEmbed: Video stopped via API.");
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
this.log("YouTubeEmbed: Using iframe postMessage for stop.");
|
|
1054
|
+
// Fallback: try to use postMessage to control iframe
|
|
1055
|
+
if (this.iframe && this.iframe.contentWindow) {
|
|
1056
|
+
this.iframe.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}', "*");
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_playing, false, "f");
|
|
1060
|
+
// Update custom control state
|
|
1061
|
+
if (this.setCustomControlState) {
|
|
1062
|
+
this.setCustomControlState(false);
|
|
1063
|
+
}
|
|
1064
|
+
this.dispatchCustomEvent("stop");
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Mutes the video audio.
|
|
1068
|
+
*/
|
|
1069
|
+
mute() {
|
|
1070
|
+
this.muted = true;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Unmutes the video audio.
|
|
1074
|
+
*/
|
|
1075
|
+
unmute() {
|
|
1076
|
+
this.muted = false;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Toggles the video between playing and paused.
|
|
1080
|
+
*/
|
|
1081
|
+
togglePlay() {
|
|
1082
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_playing, "f")) {
|
|
1083
|
+
this.pause();
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
this.play();
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Toggles the audio between muted and unmuted.
|
|
1091
|
+
*/
|
|
1092
|
+
toggleMute() {
|
|
1093
|
+
this.muted = !__classPrivateFieldGet(this, _YouTubeEmbed_muted, "f");
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Toggles the debug logging for all component instances.
|
|
1097
|
+
* @param {boolean} [forceState] - Optional: force debug mode on or off.
|
|
1098
|
+
*/
|
|
1099
|
+
static toggleDebug(forceState) {
|
|
1100
|
+
YouTubeEmbed.DEBUG =
|
|
1101
|
+
forceState !== undefined ? forceState : !YouTubeEmbed.DEBUG;
|
|
1102
|
+
console.log(`YouTubeEmbed: Debugging is now ${YouTubeEmbed.DEBUG ? "ENABLED" : "DISABLED"}.`);
|
|
1103
|
+
}
|
|
1104
|
+
// Public API for getting player state
|
|
1105
|
+
/**
|
|
1106
|
+
* Retrieves the current state of the player.
|
|
1107
|
+
* @returns An object with the current player state.
|
|
1108
|
+
*/
|
|
1109
|
+
getPlayerState() {
|
|
1110
|
+
// Get actual muted state from YouTube player if available
|
|
1111
|
+
let actualMutedState = __classPrivateFieldGet(this, _YouTubeEmbed_muted, "f");
|
|
1112
|
+
if (this.player && this.playerReady) {
|
|
1113
|
+
try {
|
|
1114
|
+
actualMutedState = this.player.isMuted();
|
|
1115
|
+
// Sync internal state with actual player state
|
|
1116
|
+
if (actualMutedState !== __classPrivateFieldGet(this, _YouTubeEmbed_muted, "f")) {
|
|
1117
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, actualMutedState, "f");
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
// Fallback to internal state if player method fails
|
|
1122
|
+
this.warn("YouTubeEmbed: Could not get muted state from player:", error);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
playing: __classPrivateFieldGet(this, _YouTubeEmbed_playing, "f"),
|
|
1127
|
+
muted: actualMutedState,
|
|
1128
|
+
videoId: __classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"),
|
|
1129
|
+
ready: this.playerReady,
|
|
1130
|
+
initialized: this.initialized,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
// Public API for checking if video is playing
|
|
1134
|
+
/**
|
|
1135
|
+
* Checks if the video is currently playing.
|
|
1136
|
+
* @returns `true` if the video is playing.
|
|
1137
|
+
*/
|
|
1138
|
+
isPlaying() {
|
|
1139
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_playing, "f");
|
|
1140
|
+
}
|
|
1141
|
+
// Public API for checking if video is muted
|
|
1142
|
+
/**
|
|
1143
|
+
* Checks if the video is currently muted.
|
|
1144
|
+
* @returns `true` if the video is muted.
|
|
1145
|
+
*/
|
|
1146
|
+
isMuted() {
|
|
1147
|
+
if (this.player && this.playerReady) {
|
|
1148
|
+
try {
|
|
1149
|
+
return this.player.isMuted();
|
|
1150
|
+
}
|
|
1151
|
+
catch (error) {
|
|
1152
|
+
this.warn("YouTubeEmbed: Could not get muted state from player:", error);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return __classPrivateFieldGet(this, _YouTubeEmbed_muted, "f");
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Request fullscreen mode
|
|
1159
|
+
*/
|
|
1160
|
+
enterFullscreen() {
|
|
1161
|
+
const elem = this;
|
|
1162
|
+
if (elem.requestFullscreen) {
|
|
1163
|
+
return elem.requestFullscreen();
|
|
1164
|
+
}
|
|
1165
|
+
else if (elem.webkitRequestFullscreen) {
|
|
1166
|
+
return elem.webkitRequestFullscreen();
|
|
1167
|
+
}
|
|
1168
|
+
else if (elem.mozRequestFullScreen) {
|
|
1169
|
+
return elem.mozRequestFullScreen();
|
|
1170
|
+
}
|
|
1171
|
+
else if (elem.msRequestFullscreen) {
|
|
1172
|
+
return elem.msRequestFullscreen();
|
|
1173
|
+
}
|
|
1174
|
+
return Promise.reject(new Error("Fullscreen API not supported"));
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Exit fullscreen mode
|
|
1178
|
+
*/
|
|
1179
|
+
exitFullscreen() {
|
|
1180
|
+
const doc = document;
|
|
1181
|
+
if (doc.exitFullscreen) {
|
|
1182
|
+
return doc.exitFullscreen();
|
|
1183
|
+
}
|
|
1184
|
+
else if (doc.webkitExitFullscreen) {
|
|
1185
|
+
return doc.webkitExitFullscreen();
|
|
1186
|
+
}
|
|
1187
|
+
else if (doc.mozCancelFullScreen) {
|
|
1188
|
+
return doc.mozCancelFullScreen();
|
|
1189
|
+
}
|
|
1190
|
+
else if (doc.msExitFullscreen) {
|
|
1191
|
+
return doc.msExitFullscreen();
|
|
1192
|
+
}
|
|
1193
|
+
return Promise.reject(new Error("Fullscreen API not supported"));
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Toggle fullscreen mode
|
|
1197
|
+
*/
|
|
1198
|
+
toggleFullscreen() {
|
|
1199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1200
|
+
if (this.isFullscreen()) {
|
|
1201
|
+
yield this.exitFullscreen();
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1204
|
+
yield this.enterFullscreen();
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Check if currently in fullscreen mode
|
|
1210
|
+
*/
|
|
1211
|
+
isFullscreen() {
|
|
1212
|
+
const doc = document;
|
|
1213
|
+
return !!(doc.fullscreenElement ||
|
|
1214
|
+
doc.webkitFullscreenElement ||
|
|
1215
|
+
doc.mozFullScreenElement ||
|
|
1216
|
+
doc.msFullscreenElement);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Get available quality levels for the current video
|
|
1220
|
+
* @returns Array of available quality levels
|
|
1221
|
+
*/
|
|
1222
|
+
getAvailableQualities() {
|
|
1223
|
+
if (this.player && this.playerReady) {
|
|
1224
|
+
try {
|
|
1225
|
+
return this.player.getAvailableQualityLevels() || [];
|
|
1226
|
+
}
|
|
1227
|
+
catch (error) {
|
|
1228
|
+
this.warn("YouTubeEmbed: Could not get available qualities:", error);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Get the current playback quality
|
|
1235
|
+
* @returns Current quality level
|
|
1236
|
+
*/
|
|
1237
|
+
getCurrentQuality() {
|
|
1238
|
+
if (this.player && this.playerReady) {
|
|
1239
|
+
try {
|
|
1240
|
+
return this.player.getPlaybackQuality() || "auto";
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
this.warn("YouTubeEmbed: Could not get current quality:", error);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return "auto";
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Set the playback quality
|
|
1250
|
+
* @param quality The desired quality level
|
|
1251
|
+
*/
|
|
1252
|
+
setQuality(quality) {
|
|
1253
|
+
if (!this.player || !this.playerReady) {
|
|
1254
|
+
this.warn("YouTubeEmbed: Player not ready for quality change");
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
try {
|
|
1258
|
+
const oldQuality = this.getCurrentQuality();
|
|
1259
|
+
this.player.setPlaybackQuality(quality);
|
|
1260
|
+
// Dispatch quality change event
|
|
1261
|
+
const event = new CustomEvent("qualitychange", {
|
|
1262
|
+
detail: {
|
|
1263
|
+
oldQuality,
|
|
1264
|
+
newQuality: quality,
|
|
1265
|
+
availableQualities: this.getAvailableQualities(),
|
|
1266
|
+
},
|
|
1267
|
+
bubbles: true,
|
|
1268
|
+
composed: true,
|
|
1269
|
+
});
|
|
1270
|
+
this.dispatchEvent(event);
|
|
1271
|
+
}
|
|
1272
|
+
catch (error) {
|
|
1273
|
+
this.warn("YouTubeEmbed: Could not set quality:", error);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
// Public API for getting current playback time
|
|
1277
|
+
/**
|
|
1278
|
+
* Gets the current playback time in seconds.
|
|
1279
|
+
* @returns The current time in seconds.
|
|
1280
|
+
*/
|
|
1281
|
+
getCurrentTime() {
|
|
1282
|
+
if (this.player && this.playerReady) {
|
|
1283
|
+
try {
|
|
1284
|
+
return this.player.getCurrentTime() || 0;
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
this.warn("YouTubeEmbed: Could not get current time from player:", error);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return 0;
|
|
1291
|
+
}
|
|
1292
|
+
// Public API for setting video by ID or URL
|
|
1293
|
+
/**
|
|
1294
|
+
* Loads a new video by its ID or URL.
|
|
1295
|
+
* @param videoIdOrUrl The 11-character video ID or the full YouTube URL.
|
|
1296
|
+
*/
|
|
1297
|
+
loadVideo(videoIdOrUrl) {
|
|
1298
|
+
// Determine if input is a full URL or a plain video ID
|
|
1299
|
+
const extracted = this.extractVideoId(videoIdOrUrl);
|
|
1300
|
+
if (extracted) {
|
|
1301
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_url, videoIdOrUrl, "f");
|
|
1302
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, extracted, "f");
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_videoId, videoIdOrUrl, "f");
|
|
1306
|
+
}
|
|
1307
|
+
// If component is lazy, load immediately when requested
|
|
1308
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_lazy, "f") && !this.player) {
|
|
1309
|
+
try {
|
|
1310
|
+
this.initializePlayer(__classPrivateFieldGet(this, _YouTubeEmbed_videoId, "f"));
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
this.warn("YouTubeEmbed: loadVideo failed to initialize player:", error);
|
|
1314
|
+
}
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
// If already initialized, reinitialize player with the new video
|
|
1318
|
+
if (this.initialized) {
|
|
1319
|
+
this.reinitializePlayer();
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
_YouTubeEmbed_url = new WeakMap(), _YouTubeEmbed_videoId = new WeakMap(), _YouTubeEmbed_autoplay = new WeakMap(), _YouTubeEmbed_muted = new WeakMap(), _YouTubeEmbed_controls = new WeakMap(), _YouTubeEmbed_lazy = new WeakMap(), _YouTubeEmbed_poster = new WeakMap(), _YouTubeEmbed_playing = new WeakMap(), _YouTubeEmbed_background = new WeakMap(), _YouTubeEmbed_playerVars = new WeakMap(), _YouTubeEmbed_instances = new WeakSet(), _YouTubeEmbed_reflectBooleanAttribute = function _YouTubeEmbed_reflectBooleanAttribute(name, value) {
|
|
1324
|
+
this.updatingAttribute = true;
|
|
1325
|
+
if (value) {
|
|
1326
|
+
this.setAttribute(name, "");
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
this.removeAttribute(name);
|
|
1330
|
+
}
|
|
1331
|
+
this.updatingAttribute = false;
|
|
1332
|
+
}, _YouTubeEmbed_updateBackgroundMode = function _YouTubeEmbed_updateBackgroundMode() {
|
|
1333
|
+
if (__classPrivateFieldGet(this, _YouTubeEmbed_background, "f")) {
|
|
1334
|
+
this.classList.add("is-background");
|
|
1335
|
+
// Ensure properties for background mode are set
|
|
1336
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_autoplay, true, "f");
|
|
1337
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_muted, true, "f");
|
|
1338
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_controls, false, "f");
|
|
1339
|
+
__classPrivateFieldSet(this, _YouTubeEmbed_lazy, false, "f"); // Background videos should load immediately
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
this.classList.remove("is-background");
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
YouTubeEmbed.apiLoaded = false;
|
|
1346
|
+
YouTubeEmbed.apiReady = false;
|
|
1347
|
+
YouTubeEmbed.instanceCount = 0; // Track active instances
|
|
1348
|
+
YouTubeEmbed.DEBUG = false; // Enable/disable debug logging
|
|
1349
|
+
YouTubeEmbed.MAX_API_RETRIES = 3;
|
|
1350
|
+
YouTubeEmbed.API_RETRY_DELAY = 2000;
|
|
1351
|
+
YouTubeEmbed.API_LOAD_TIMEOUT = 10000;
|
|
1352
|
+
customElements.define("youtube-embed", YouTubeEmbed);
|
|
1353
|
+
// Component ready for testing
|
|
1354
|
+
//# sourceMappingURL=YouTubeEmbed.js.map
|