unified-video-framework 1.4.372 → 1.4.374
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/package.json +1 -1
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +21 -3
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/ads/GoogleAdsManager.d.ts +1 -1
- package/packages/web/dist/ads/GoogleAdsManager.d.ts.map +1 -1
- package/packages/web/dist/ads/GoogleAdsManager.js +38 -6
- package/packages/web/dist/ads/GoogleAdsManager.js.map +1 -1
- package/packages/web/dist/chapters/CreditsButtonController.js +1 -1
- package/packages/web/dist/chapters/CreditsButtonController.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +95 -14
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +21 -3
- package/packages/web/src/ads/GoogleAdsManager.ts +116 -64
- package/packages/web/src/chapters/CreditsButtonController.ts +1 -1
- package/packages/web/src/react/WebPlayerView.tsx +125 -29
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
export interface GoogleAdsConfig {
|
|
14
14
|
// Ad tag URL (VAST/VMAP)
|
|
15
15
|
adTagUrl: string;
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
// Optional: Specific ad break times (for mid-rolls)
|
|
18
18
|
// If not provided, uses VMAP schedule from ad server
|
|
19
19
|
midrollTimes?: number[]; // e.g., [30, 60, 120] = ads at 30s, 60s, 120s
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
// Companion ad containers
|
|
22
22
|
companionAdSlots?: Array<{
|
|
23
23
|
containerId: string; // HTML element ID
|
|
24
24
|
width: number;
|
|
25
25
|
height: number;
|
|
26
26
|
}>;
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// Callbacks
|
|
29
29
|
onAdStart?: () => void;
|
|
30
30
|
onAdEnd?: () => void;
|
|
@@ -48,11 +48,11 @@ export class GoogleAdsManager {
|
|
|
48
48
|
this.video = video;
|
|
49
49
|
this.adContainer = adContainer;
|
|
50
50
|
this.config = config;
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
// Add focus handler to resume ads after click-through
|
|
53
53
|
this.setupFocusHandler();
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
/**
|
|
57
57
|
* Setup focus handler to resume ads when window regains focus
|
|
58
58
|
*/
|
|
@@ -66,7 +66,7 @@ export class GoogleAdsManager {
|
|
|
66
66
|
setTimeout(() => this.resumeAdPlayback(), 200);
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
// Also check visibility change (tab switch)
|
|
71
71
|
document.addEventListener('visibilitychange', () => {
|
|
72
72
|
if (!document.hidden && this.isAdPlaying) {
|
|
@@ -113,30 +113,30 @@ export class GoogleAdsManager {
|
|
|
113
113
|
*/
|
|
114
114
|
private setupAdsLoader(): void {
|
|
115
115
|
const google = (window as any).google;
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
// Create ad display container
|
|
118
118
|
this.adDisplayContainer = new google.ima.AdDisplayContainer(
|
|
119
119
|
this.adContainer,
|
|
120
120
|
this.video
|
|
121
121
|
);
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
// Create ads loader
|
|
124
124
|
this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
// Register for ads loaded event
|
|
127
127
|
this.adsLoader.addEventListener(
|
|
128
128
|
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
|
129
129
|
(event: any) => this.onAdsManagerLoaded(event),
|
|
130
130
|
false
|
|
131
131
|
);
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
// Register for error event
|
|
134
134
|
this.adsLoader.addEventListener(
|
|
135
135
|
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
136
136
|
(event: any) => this.onAdError(event),
|
|
137
137
|
false
|
|
138
138
|
);
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
// Signal when video content completes (for post-rolls)
|
|
141
141
|
this.video.addEventListener('ended', () => {
|
|
142
142
|
if (!this.isAdPlaying) {
|
|
@@ -150,26 +150,29 @@ export class GoogleAdsManager {
|
|
|
150
150
|
*/
|
|
151
151
|
requestAds(): void {
|
|
152
152
|
const google = (window as any).google;
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
try {
|
|
155
155
|
const adsRequest = new google.ima.AdsRequest();
|
|
156
156
|
adsRequest.adTagUrl = this.config.adTagUrl;
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
// Set video dimensions
|
|
159
159
|
adsRequest.linearAdSlotWidth = this.video.clientWidth;
|
|
160
160
|
adsRequest.linearAdSlotHeight = this.video.clientHeight;
|
|
161
161
|
adsRequest.nonLinearAdSlotWidth = this.video.clientWidth;
|
|
162
162
|
adsRequest.nonLinearAdSlotHeight = Math.floor(this.video.clientHeight / 3);
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
// Set companion ad slots if provided
|
|
165
165
|
if (this.config.companionAdSlots && this.config.companionAdSlots.length > 0) {
|
|
166
166
|
const companionAdSlots = this.config.companionAdSlots.map(slot => {
|
|
167
167
|
return new google.ima.CompanionAdSelectionSettings();
|
|
168
168
|
});
|
|
169
|
-
adsRequest.setAdWillAutoPlay(true);
|
|
170
|
-
adsRequest.setAdWillPlayMuted(false);
|
|
171
169
|
}
|
|
172
|
-
|
|
170
|
+
|
|
171
|
+
// Chrome autoplay policy: ads must start muted for autoplay to work
|
|
172
|
+
// User can unmute after ad starts
|
|
173
|
+
adsRequest.setAdWillAutoPlay(true);
|
|
174
|
+
adsRequest.setAdWillPlayMuted(true); // Start muted for Chrome compatibility
|
|
175
|
+
|
|
173
176
|
// Request ads
|
|
174
177
|
this.adsLoader.requestAds(adsRequest);
|
|
175
178
|
} catch (error) {
|
|
@@ -194,16 +197,17 @@ export class GoogleAdsManager {
|
|
|
194
197
|
*/
|
|
195
198
|
private onAdsManagerLoaded(event: any): void {
|
|
196
199
|
const google = (window as any).google;
|
|
197
|
-
|
|
200
|
+
|
|
198
201
|
// Setup ads rendering settings
|
|
199
202
|
const adsRenderingSettings = new google.ima.AdsRenderingSettings();
|
|
200
203
|
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
|
|
201
204
|
adsRenderingSettings.enablePreloading = true;
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
// Start muted for Chrome autoplay compatibility - user can unmute via button
|
|
206
|
+
adsRenderingSettings.mute = this.video.muted;
|
|
207
|
+
|
|
204
208
|
// Get the ads manager
|
|
205
209
|
this.adsManager = event.getAdsManager(this.video, adsRenderingSettings);
|
|
206
|
-
|
|
210
|
+
|
|
207
211
|
// Extract cue points (ad break times) from VMAP/ad server
|
|
208
212
|
try {
|
|
209
213
|
const cuePoints = this.adsManager.getCuePoints();
|
|
@@ -215,9 +219,9 @@ export class GoogleAdsManager {
|
|
|
215
219
|
if (time === -1) return -1; // Post-roll (will be converted to video duration later)
|
|
216
220
|
return time; // Mid-roll at specific time
|
|
217
221
|
});
|
|
218
|
-
|
|
222
|
+
|
|
219
223
|
console.log('📍 Ad cue points detected (pre/mid/post):', allCuePoints);
|
|
220
|
-
|
|
224
|
+
|
|
221
225
|
// Notify callback with all cue points
|
|
222
226
|
if (this.config.onAdCuePoints) {
|
|
223
227
|
this.config.onAdCuePoints(allCuePoints);
|
|
@@ -226,10 +230,10 @@ export class GoogleAdsManager {
|
|
|
226
230
|
} catch (error) {
|
|
227
231
|
console.warn('Could not extract ad cue points:', error);
|
|
228
232
|
}
|
|
229
|
-
|
|
233
|
+
|
|
230
234
|
// Setup ads manager event listeners
|
|
231
235
|
this.setupAdsManagerListeners();
|
|
232
|
-
|
|
236
|
+
|
|
233
237
|
try {
|
|
234
238
|
// Initialize ads manager
|
|
235
239
|
this.adsManager.init(
|
|
@@ -237,12 +241,12 @@ export class GoogleAdsManager {
|
|
|
237
241
|
this.video.clientHeight,
|
|
238
242
|
google.ima.ViewMode.NORMAL
|
|
239
243
|
);
|
|
240
|
-
|
|
244
|
+
|
|
241
245
|
// Start ads
|
|
242
246
|
this.adsManager.start();
|
|
243
247
|
} catch (error) {
|
|
244
248
|
console.error('Error starting ads:', error);
|
|
245
|
-
this.video.play().catch(() => {});
|
|
249
|
+
this.video.play().catch(() => { });
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
|
|
@@ -251,7 +255,7 @@ export class GoogleAdsManager {
|
|
|
251
255
|
*/
|
|
252
256
|
private setupAdsManagerListeners(): void {
|
|
253
257
|
const google = (window as any).google;
|
|
254
|
-
|
|
258
|
+
|
|
255
259
|
// Content pause - ad is about to play
|
|
256
260
|
this.adsManager.addEventListener(
|
|
257
261
|
google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
|
|
@@ -259,21 +263,54 @@ export class GoogleAdsManager {
|
|
|
259
263
|
console.log('Ad: Content paused');
|
|
260
264
|
this.isAdPlaying = true;
|
|
261
265
|
this.video.pause();
|
|
266
|
+
|
|
267
|
+
// Force ad container visibility and z-index
|
|
268
|
+
if (this.adContainer) {
|
|
269
|
+
this.adContainer.style.visibility = 'visible';
|
|
270
|
+
this.adContainer.style.opacity = '1';
|
|
271
|
+
this.adContainer.style.pointerEvents = 'auto';
|
|
272
|
+
this.adContainer.style.zIndex = '2147483647';
|
|
273
|
+
console.log('✅ Ad container visibility enforced');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Strict enforcement: Prevent video from playing during ads
|
|
277
|
+
const preventPlayDuringAd = (e: Event) => {
|
|
278
|
+
if (this.isAdPlaying) {
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
this.video.pause();
|
|
281
|
+
console.warn('Blocked video play attempt during ad');
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Add play event listener to block any play attempts
|
|
286
|
+
this.video.addEventListener('play', preventPlayDuringAd);
|
|
287
|
+
|
|
288
|
+
// Store cleanup function to remove listener later
|
|
289
|
+
(this.video as any).__adPlayBlocker = preventPlayDuringAd;
|
|
290
|
+
|
|
262
291
|
this.config.onAdStart?.();
|
|
263
292
|
}
|
|
264
293
|
);
|
|
265
|
-
|
|
294
|
+
|
|
266
295
|
// Content resume - ad finished
|
|
267
296
|
this.adsManager.addEventListener(
|
|
268
297
|
google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
|
|
269
298
|
() => {
|
|
270
299
|
console.log('Ad: Content resume');
|
|
271
300
|
this.isAdPlaying = false;
|
|
301
|
+
|
|
302
|
+
// Remove play blocker
|
|
303
|
+
const preventPlayDuringAd = (this.video as any).__adPlayBlocker;
|
|
304
|
+
if (preventPlayDuringAd) {
|
|
305
|
+
this.video.removeEventListener('play', preventPlayDuringAd);
|
|
306
|
+
delete (this.video as any).__adPlayBlocker;
|
|
307
|
+
}
|
|
308
|
+
|
|
272
309
|
this.config.onAdEnd?.();
|
|
273
|
-
this.video.play().catch(() => {});
|
|
310
|
+
this.video.play().catch(() => { });
|
|
274
311
|
}
|
|
275
312
|
);
|
|
276
|
-
|
|
313
|
+
|
|
277
314
|
// Ad started
|
|
278
315
|
this.adsManager.addEventListener(
|
|
279
316
|
google.ima.AdEvent.Type.STARTED,
|
|
@@ -285,19 +322,19 @@ export class GoogleAdsManager {
|
|
|
285
322
|
skippable: ad.getSkipTimeOffset() !== -1,
|
|
286
323
|
title: ad.getTitle(),
|
|
287
324
|
});
|
|
288
|
-
|
|
325
|
+
|
|
289
326
|
// Sync ads mute state with video element
|
|
290
327
|
this.isMuted = this.video.muted;
|
|
291
328
|
console.log(`Ad started - video.muted=${this.video.muted}, isMuted=${this.isMuted}`);
|
|
292
|
-
|
|
329
|
+
|
|
293
330
|
// Check if video is actually muted and show unmute button only then
|
|
294
331
|
if (this.isMuted) {
|
|
295
332
|
this.showUnmuteButton();
|
|
296
333
|
}
|
|
297
|
-
|
|
334
|
+
|
|
298
335
|
}
|
|
299
336
|
);
|
|
300
|
-
|
|
337
|
+
|
|
301
338
|
// Ad completed
|
|
302
339
|
this.adsManager.addEventListener(
|
|
303
340
|
google.ima.AdEvent.Type.COMPLETE,
|
|
@@ -305,7 +342,7 @@ export class GoogleAdsManager {
|
|
|
305
342
|
console.log('Ad completed');
|
|
306
343
|
}
|
|
307
344
|
);
|
|
308
|
-
|
|
345
|
+
|
|
309
346
|
// All ads completed
|
|
310
347
|
this.adsManager.addEventListener(
|
|
311
348
|
google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
|
|
@@ -314,13 +351,13 @@ export class GoogleAdsManager {
|
|
|
314
351
|
this.config.onAllAdsComplete?.();
|
|
315
352
|
}
|
|
316
353
|
);
|
|
317
|
-
|
|
354
|
+
|
|
318
355
|
// Ad error
|
|
319
356
|
this.adsManager.addEventListener(
|
|
320
357
|
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
321
358
|
(event: any) => this.onAdError(event)
|
|
322
359
|
);
|
|
323
|
-
|
|
360
|
+
|
|
324
361
|
// Ad skipped
|
|
325
362
|
this.adsManager.addEventListener(
|
|
326
363
|
google.ima.AdEvent.Type.SKIPPED,
|
|
@@ -328,7 +365,7 @@ export class GoogleAdsManager {
|
|
|
328
365
|
console.log('Ad skipped by user');
|
|
329
366
|
}
|
|
330
367
|
);
|
|
331
|
-
|
|
368
|
+
|
|
332
369
|
// Ad paused (by click-through)
|
|
333
370
|
this.adsManager.addEventListener(
|
|
334
371
|
google.ima.AdEvent.Type.PAUSED,
|
|
@@ -337,7 +374,7 @@ export class GoogleAdsManager {
|
|
|
337
374
|
// Don't mark as not playing - keep isAdPlaying true so we can resume
|
|
338
375
|
}
|
|
339
376
|
);
|
|
340
|
-
|
|
377
|
+
|
|
341
378
|
// Ad playing (resume after pause)
|
|
342
379
|
this.adsManager.addEventListener(
|
|
343
380
|
google.ima.AdEvent.Type.PLAYING,
|
|
@@ -353,17 +390,17 @@ export class GoogleAdsManager {
|
|
|
353
390
|
private onAdError(event: any): void {
|
|
354
391
|
const error = event.getError?.();
|
|
355
392
|
console.error('Ad error:', error?.getMessage?.() || error);
|
|
356
|
-
|
|
393
|
+
|
|
357
394
|
this.config.onAdError?.(error);
|
|
358
|
-
|
|
395
|
+
|
|
359
396
|
// Destroy ads manager on error
|
|
360
397
|
if (this.adsManager) {
|
|
361
398
|
this.adsManager.destroy();
|
|
362
399
|
}
|
|
363
|
-
|
|
400
|
+
|
|
364
401
|
// Resume content playback
|
|
365
402
|
this.isAdPlaying = false;
|
|
366
|
-
this.video.play().catch(() => {});
|
|
403
|
+
this.video.play().catch(() => { });
|
|
367
404
|
}
|
|
368
405
|
|
|
369
406
|
/**
|
|
@@ -394,12 +431,27 @@ export class GoogleAdsManager {
|
|
|
394
431
|
}
|
|
395
432
|
|
|
396
433
|
/**
|
|
397
|
-
* Resize ads
|
|
434
|
+
* Resize ads - with enhanced fullscreen support
|
|
398
435
|
*/
|
|
399
|
-
resize(width: number, height: number): void {
|
|
436
|
+
resize(width: number, height: number, viewMode?: any): void {
|
|
400
437
|
const google = (window as any).google;
|
|
401
|
-
if (this.adsManager) {
|
|
402
|
-
|
|
438
|
+
if (this.adsManager && google && google.ima) {
|
|
439
|
+
const mode = viewMode || google.ima.ViewMode.NORMAL;
|
|
440
|
+
|
|
441
|
+
console.log(`📐 Resizing ads: ${width}x${height}, ViewMode: ${mode === google.ima.ViewMode.FULLSCREEN ? 'FULLSCREEN' : 'NORMAL'}`);
|
|
442
|
+
|
|
443
|
+
// Force ad container dimensions in fullscreen
|
|
444
|
+
if (this.adContainer && mode === google.ima.ViewMode.FULLSCREEN) {
|
|
445
|
+
this.adContainer.style.position = 'fixed';
|
|
446
|
+
this.adContainer.style.top = '0';
|
|
447
|
+
this.adContainer.style.left = '0';
|
|
448
|
+
this.adContainer.style.width = `${width}px`;
|
|
449
|
+
this.adContainer.style.height = `${height}px`;
|
|
450
|
+
this.adContainer.style.zIndex = '2147483647';
|
|
451
|
+
console.log('✅ Ad container forced to fullscreen dimensions');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
this.adsManager.resize(width, height, mode);
|
|
403
455
|
}
|
|
404
456
|
}
|
|
405
457
|
|
|
@@ -427,7 +479,7 @@ export class GoogleAdsManager {
|
|
|
427
479
|
if (this.unmuteButton) {
|
|
428
480
|
this.unmuteButton.remove();
|
|
429
481
|
}
|
|
430
|
-
|
|
482
|
+
|
|
431
483
|
// Create unmute button (matching WebPlayer.ts style)
|
|
432
484
|
this.unmuteButton = document.createElement('button');
|
|
433
485
|
this.unmuteButton.id = 'ad-unmute-btn';
|
|
@@ -439,13 +491,13 @@ export class GoogleAdsManager {
|
|
|
439
491
|
</svg>
|
|
440
492
|
<span class="uvf-unmute-text">Tap to unmute</span>
|
|
441
493
|
`;
|
|
442
|
-
|
|
494
|
+
|
|
443
495
|
// Click handler to unmute
|
|
444
496
|
this.unmuteButton.addEventListener('click', (e) => {
|
|
445
497
|
e.stopPropagation();
|
|
446
498
|
this.toggleAdMute();
|
|
447
499
|
});
|
|
448
|
-
|
|
500
|
+
|
|
449
501
|
// Add styles if not already added
|
|
450
502
|
if (!document.getElementById('uvf-unmute-styles')) {
|
|
451
503
|
const style = document.createElement('style');
|
|
@@ -517,33 +569,33 @@ export class GoogleAdsManager {
|
|
|
517
569
|
`;
|
|
518
570
|
document.head.appendChild(style);
|
|
519
571
|
}
|
|
520
|
-
|
|
572
|
+
|
|
521
573
|
// Add to ad container
|
|
522
574
|
this.adContainer.appendChild(this.unmuteButton);
|
|
523
575
|
console.log('Unmute button displayed (matching player style)');
|
|
524
576
|
}
|
|
525
|
-
|
|
577
|
+
|
|
526
578
|
/**
|
|
527
579
|
* Toggle ad mute state
|
|
528
580
|
*/
|
|
529
581
|
private toggleAdMute(): void {
|
|
530
582
|
this.isMuted = !this.isMuted;
|
|
531
|
-
|
|
583
|
+
|
|
532
584
|
if (this.adsManager) {
|
|
533
585
|
this.adsManager.setVolume(this.isMuted ? 0 : 1);
|
|
534
586
|
console.log(`Ad ${this.isMuted ? 'muted' : 'unmuted'}`);
|
|
535
587
|
}
|
|
536
|
-
|
|
588
|
+
|
|
537
589
|
// Sync mute state with video element
|
|
538
590
|
this.video.muted = this.isMuted;
|
|
539
|
-
|
|
591
|
+
|
|
540
592
|
// Hide button if unmuted
|
|
541
593
|
if (!this.isMuted && this.unmuteButton) {
|
|
542
594
|
this.unmuteButton.remove();
|
|
543
595
|
this.unmuteButton = null;
|
|
544
596
|
}
|
|
545
597
|
}
|
|
546
|
-
|
|
598
|
+
|
|
547
599
|
/**
|
|
548
600
|
* Resume ad playback after click-through
|
|
549
601
|
*/
|
|
@@ -553,9 +605,9 @@ export class GoogleAdsManager {
|
|
|
553
605
|
if (!this.adsManager || !this.isAdPlaying) {
|
|
554
606
|
return;
|
|
555
607
|
}
|
|
556
|
-
|
|
608
|
+
|
|
557
609
|
console.log('Attempting to resume ad playback...');
|
|
558
|
-
|
|
610
|
+
|
|
559
611
|
// Try multiple resume methods
|
|
560
612
|
try {
|
|
561
613
|
this.adsManager.resume();
|
|
@@ -563,7 +615,7 @@ export class GoogleAdsManager {
|
|
|
563
615
|
} catch (e) {
|
|
564
616
|
console.warn('resume() failed:', e);
|
|
565
617
|
}
|
|
566
|
-
|
|
618
|
+
|
|
567
619
|
// Also try playing the video element directly
|
|
568
620
|
if (this.video) {
|
|
569
621
|
try {
|
|
@@ -584,7 +636,7 @@ export class GoogleAdsManager {
|
|
|
584
636
|
console.error('Error resuming ad playback:', error);
|
|
585
637
|
}
|
|
586
638
|
}
|
|
587
|
-
|
|
639
|
+
|
|
588
640
|
/**
|
|
589
641
|
* Hide unmute button
|
|
590
642
|
*/
|
|
@@ -600,17 +652,17 @@ export class GoogleAdsManager {
|
|
|
600
652
|
*/
|
|
601
653
|
destroy(): void {
|
|
602
654
|
this.hideUnmuteButton();
|
|
603
|
-
|
|
655
|
+
|
|
604
656
|
if (this.adsManager) {
|
|
605
657
|
this.adsManager.destroy();
|
|
606
658
|
this.adsManager = null;
|
|
607
659
|
}
|
|
608
|
-
|
|
660
|
+
|
|
609
661
|
if (this.adsLoader) {
|
|
610
662
|
this.adsLoader.destroy();
|
|
611
663
|
this.adsLoader = null;
|
|
612
664
|
}
|
|
613
|
-
|
|
665
|
+
|
|
614
666
|
this.isAdPlaying = false;
|
|
615
667
|
}
|
|
616
668
|
}
|
|
@@ -531,9 +531,41 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
531
531
|
|
|
532
532
|
// Google Ads state
|
|
533
533
|
const adsManagerRef = useRef<GoogleAdsManager | null>(null);
|
|
534
|
-
const adContainerRef = useRef<HTMLDivElement>(null);
|
|
534
|
+
const adContainerRef = useRef<HTMLDivElement | null>(null);
|
|
535
535
|
const [isAdPlaying, setIsAdPlaying] = useState(false);
|
|
536
|
-
|
|
536
|
+
|
|
537
|
+
// Create ad container element programmatically
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
if (!props.googleAds || adContainerRef.current) return;
|
|
540
|
+
|
|
541
|
+
const adContainer = document.createElement('div');
|
|
542
|
+
adContainer.className = 'uvf-ad-container';
|
|
543
|
+
adContainer.style.cssText = `
|
|
544
|
+
position: absolute;
|
|
545
|
+
top: 0;
|
|
546
|
+
left: 0;
|
|
547
|
+
right: 0;
|
|
548
|
+
bottom: 0;
|
|
549
|
+
z-index: 999999999;
|
|
550
|
+
pointer-events: none;
|
|
551
|
+
visibility: hidden;
|
|
552
|
+
opacity: 0;
|
|
553
|
+
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
554
|
+
`;
|
|
555
|
+
|
|
556
|
+
adContainerRef.current = adContainer;
|
|
557
|
+
console.log('✅ Ad container element created');
|
|
558
|
+
}, [props.googleAds]);
|
|
559
|
+
|
|
560
|
+
// Update ad container visibility when isAdPlaying changes
|
|
561
|
+
useEffect(() => {
|
|
562
|
+
if (!adContainerRef.current) return;
|
|
563
|
+
|
|
564
|
+
adContainerRef.current.style.pointerEvents = isAdPlaying ? 'auto' : 'none';
|
|
565
|
+
adContainerRef.current.style.visibility = isAdPlaying ? 'visible' : 'hidden';
|
|
566
|
+
adContainerRef.current.style.opacity = isAdPlaying ? '1' : '0';
|
|
567
|
+
}, [isAdPlaying]);
|
|
568
|
+
|
|
537
569
|
/**
|
|
538
570
|
* Generate ad chapter segments from googleAds cue points
|
|
539
571
|
* Handles pre-roll (0), mid-rolls, and post-roll (-1)
|
|
@@ -1165,8 +1197,59 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1165
1197
|
if (props.onBuffering && typeof (player as any).on === 'function') {
|
|
1166
1198
|
(player as any).on('onBuffering', props.onBuffering);
|
|
1167
1199
|
}
|
|
1168
|
-
if (
|
|
1169
|
-
(player as any).on('onFullscreenChanged',
|
|
1200
|
+
if (typeof (player as any).on === 'function') {
|
|
1201
|
+
(player as any).on('onFullscreenChanged', (isFullscreen: boolean) => {
|
|
1202
|
+
console.log(`🔄 Fullscreen changed: ${isFullscreen}`);
|
|
1203
|
+
|
|
1204
|
+
// Force ad container positioning for fullscreen
|
|
1205
|
+
if (adContainerRef.current) {
|
|
1206
|
+
const adContainer = adContainerRef.current;
|
|
1207
|
+
|
|
1208
|
+
if (isFullscreen) {
|
|
1209
|
+
// Fullscreen: use fixed positioning
|
|
1210
|
+
adContainer.style.position = 'fixed';
|
|
1211
|
+
adContainer.style.top = '0';
|
|
1212
|
+
adContainer.style.left = '0';
|
|
1213
|
+
adContainer.style.width = '100vw';
|
|
1214
|
+
adContainer.style.height = '100vh';
|
|
1215
|
+
adContainer.style.zIndex = '2147483647';
|
|
1216
|
+
console.log('✅ Ad container: fullscreen positioning applied');
|
|
1217
|
+
} else {
|
|
1218
|
+
// Normal: use absolute positioning
|
|
1219
|
+
adContainer.style.position = 'absolute';
|
|
1220
|
+
adContainer.style.top = '0';
|
|
1221
|
+
adContainer.style.left = '0';
|
|
1222
|
+
adContainer.style.right = '0';
|
|
1223
|
+
adContainer.style.bottom = '0';
|
|
1224
|
+
adContainer.style.width = '';
|
|
1225
|
+
adContainer.style.height = '';
|
|
1226
|
+
adContainer.style.zIndex = '999999999';
|
|
1227
|
+
console.log('✅ Ad container: normal positioning restored');
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Resize Google Ads if active
|
|
1232
|
+
if (adsManagerRef.current) {
|
|
1233
|
+
const google = (window as any).google;
|
|
1234
|
+
if (google && google.ima) {
|
|
1235
|
+
const width = isFullscreen
|
|
1236
|
+
? window.screen.width
|
|
1237
|
+
: (containerRef.current?.clientWidth || window.innerWidth);
|
|
1238
|
+
const height = isFullscreen
|
|
1239
|
+
? window.screen.height
|
|
1240
|
+
: (containerRef.current?.clientHeight || window.innerHeight);
|
|
1241
|
+
const viewMode = isFullscreen
|
|
1242
|
+
? google.ima.ViewMode.FULLSCREEN
|
|
1243
|
+
: google.ima.ViewMode.NORMAL;
|
|
1244
|
+
|
|
1245
|
+
adsManagerRef.current.resize(width, height, viewMode);
|
|
1246
|
+
console.log(`✅ Ads resized: ${width}x${height}, ViewMode: ${viewMode === google.ima.ViewMode.FULLSCREEN ? 'FULLSCREEN' : 'NORMAL'}`);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Call user's fullscreen callback
|
|
1251
|
+
props.onFullscreenChange?.(isFullscreen);
|
|
1252
|
+
});
|
|
1170
1253
|
}
|
|
1171
1254
|
if (props.onPictureInPictureChange && typeof (player as any).on === 'function') {
|
|
1172
1255
|
(player as any).on('onPictureInPicturechange', props.onPictureInPictureChange);
|
|
@@ -1249,18 +1332,47 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1249
1332
|
|
|
1250
1333
|
await adsManager.initialize();
|
|
1251
1334
|
adsManagerRef.current = adsManager;
|
|
1252
|
-
|
|
1335
|
+
|
|
1253
1336
|
console.log('Google Ads initialized successfully');
|
|
1254
|
-
|
|
1255
|
-
//
|
|
1337
|
+
|
|
1338
|
+
// Inject ad container into player wrapper (not as sibling)
|
|
1339
|
+
const playerWrapper = containerRef.current?.querySelector('.uvf-player-wrapper');
|
|
1340
|
+
if (playerWrapper && adContainerRef.current) {
|
|
1341
|
+
// Move ad container INSIDE player wrapper so it inherits fullscreen context
|
|
1342
|
+
playerWrapper.appendChild(adContainerRef.current);
|
|
1343
|
+
console.log('✅ Ad container injected into player wrapper');
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// Initialize ad display container on first user interaction
|
|
1347
|
+
// Chrome requires this to be called on a user gesture
|
|
1348
|
+
let adContainerInitialized = false;
|
|
1349
|
+
|
|
1350
|
+
const initAdsOnUserGesture = () => {
|
|
1351
|
+
if (!adContainerInitialized && adsManagerRef.current) {
|
|
1352
|
+
console.log('Initializing ad container on user gesture');
|
|
1353
|
+
try {
|
|
1354
|
+
adsManagerRef.current.initAdDisplayContainer();
|
|
1355
|
+
adContainerInitialized = true;
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
console.warn('Ad container init error:', err);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1256
1362
|
const handleFirstPlay = () => {
|
|
1257
|
-
|
|
1258
|
-
|
|
1363
|
+
initAdsOnUserGesture();
|
|
1364
|
+
if (adsManagerRef.current && adContainerInitialized) {
|
|
1259
1365
|
adsManagerRef.current.requestAds();
|
|
1260
1366
|
}
|
|
1261
1367
|
videoElement.removeEventListener('play', handleFirstPlay);
|
|
1368
|
+
videoElement.removeEventListener('click', initAdsOnUserGesture);
|
|
1369
|
+
adContainerRef.current?.removeEventListener('click', initAdsOnUserGesture);
|
|
1262
1370
|
};
|
|
1371
|
+
|
|
1372
|
+
// Listen for both play and click events to ensure we catch user gesture
|
|
1263
1373
|
videoElement.addEventListener('play', handleFirstPlay, { once: true });
|
|
1374
|
+
videoElement.addEventListener('click', initAdsOnUserGesture, { once: true });
|
|
1375
|
+
adContainerRef.current?.addEventListener('click', initAdsOnUserGesture, { once: true });
|
|
1264
1376
|
} catch (adsError) {
|
|
1265
1377
|
console.error('Failed to initialize Google Ads:', adsError);
|
|
1266
1378
|
props.googleAds?.onAdError?.(adsError);
|
|
@@ -1597,29 +1709,13 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1597
1709
|
}}
|
|
1598
1710
|
>
|
|
1599
1711
|
{/* Video Player */}
|
|
1600
|
-
<div
|
|
1601
|
-
ref={containerRef}
|
|
1712
|
+
<div
|
|
1713
|
+
ref={containerRef}
|
|
1602
1714
|
className={`uvf-responsive-container ${props.className || ''}`}
|
|
1603
1715
|
style={responsiveStyle}
|
|
1604
1716
|
/>
|
|
1605
|
-
|
|
1606
|
-
{/*
|
|
1607
|
-
{props.googleAds && (
|
|
1608
|
-
<div
|
|
1609
|
-
ref={adContainerRef}
|
|
1610
|
-
className="uvf-ad-container"
|
|
1611
|
-
style={{
|
|
1612
|
-
position: 'fixed',
|
|
1613
|
-
top: 0,
|
|
1614
|
-
left: 0,
|
|
1615
|
-
right: 0,
|
|
1616
|
-
bottom: 0,
|
|
1617
|
-
zIndex: 2147483647, // Maximum z-index - ads must be on top of everything
|
|
1618
|
-
pointerEvents: isAdPlaying ? 'auto' : 'none', // Allow interaction only when ad is playing
|
|
1619
|
-
display: isAdPlaying ? 'block' : 'none', // Hide container when no ad
|
|
1620
|
-
}}
|
|
1621
|
-
/>
|
|
1622
|
-
)}
|
|
1717
|
+
|
|
1718
|
+
{/* Ad container will be programmatically injected into player wrapper */}
|
|
1623
1719
|
|
|
1624
1720
|
|
|
1625
1721
|
{/* EPG Overlay - Full-screen glassmorphic overlay */}
|