unified-video-framework 1.4.404 → 1.4.406
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/ads/GoogleAdsManager.d.ts +13 -6
- package/packages/web/dist/ads/GoogleAdsManager.d.ts.map +1 -1
- package/packages/web/dist/ads/GoogleAdsManager.js +41 -89
- package/packages/web/dist/ads/GoogleAdsManager.js.map +1 -1
- package/packages/web/dist/ads/LiveStreamAdsManager.d.ts +4 -25
- package/packages/web/dist/ads/LiveStreamAdsManager.d.ts.map +1 -1
- package/packages/web/dist/ads/LiveStreamAdsManager.js +33 -224
- package/packages/web/dist/ads/LiveStreamAdsManager.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +5 -12
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +66 -156
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/ads/GoogleAdsManager.ts +80 -135
- package/packages/web/src/react/WebPlayerView.tsx +30 -166
- package/packages/web/src/ads/LiveStreamAdsManager.ts +0 -625
- package/packages/web/src/react/examples/live-stream-ads-example.tsx +0 -362
|
@@ -18,6 +18,13 @@ export interface GoogleAdsConfig {
|
|
|
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
|
+
// Periodic ad breaks (for live streams or automatic scheduling)
|
|
22
|
+
liveAdBreakMode?: 'periodic' | 'manual'; // 'periodic' = auto-schedule at intervals, 'manual' = use midrollTimes
|
|
23
|
+
periodicAdInterval?: number; // Interval in seconds between ads (e.g., 30 = ad every 30 seconds)
|
|
24
|
+
syncToLiveEdge?: boolean; // For live streams: sync back to live edge after ad
|
|
25
|
+
pauseStreamDuringAd?: boolean; // Pause underlying stream during ad playback
|
|
26
|
+
liveEdgeOffset?: number; // Seconds behind live edge to resume at (default: 3)
|
|
27
|
+
|
|
21
28
|
// Companion ad containers
|
|
22
29
|
companionAdSlots?: Array<{
|
|
23
30
|
containerId: string; // HTML element ID
|
|
@@ -34,16 +41,20 @@ export interface GoogleAdsConfig {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export class GoogleAdsManager {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
private video: HTMLVideoElement;
|
|
45
|
+
private adContainer: HTMLElement;
|
|
46
|
+
private config: GoogleAdsConfig;
|
|
40
47
|
private adsManager: any = null;
|
|
41
48
|
private adsLoader: any = null;
|
|
42
49
|
private adDisplayContainer: any = null;
|
|
43
50
|
private isAdPlaying = false;
|
|
44
51
|
private isMuted = true;
|
|
45
52
|
private unmuteButton: HTMLElement | null = null;
|
|
46
|
-
|
|
53
|
+
|
|
54
|
+
// Periodic ad scheduling
|
|
55
|
+
private lastAdTime = 0;
|
|
56
|
+
private periodicAdCheckInterval: any = null;
|
|
57
|
+
private pendingAdRequest = false;
|
|
47
58
|
|
|
48
59
|
constructor(video: HTMLVideoElement, adContainer: HTMLElement, config: GoogleAdsConfig) {
|
|
49
60
|
this.video = video;
|
|
@@ -146,6 +157,59 @@ export class GoogleAdsManager {
|
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Setup periodic ad breaks (for live streams or auto-scheduling)
|
|
162
|
+
*/
|
|
163
|
+
setupPeriodicAds(): void {
|
|
164
|
+
// Only setup if periodic mode is enabled
|
|
165
|
+
if (this.config.liveAdBreakMode !== 'periodic' || !this.config.periodicAdInterval) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`📺 Setting up periodic ads: interval=${this.config.periodicAdInterval}s`);
|
|
170
|
+
|
|
171
|
+
// Check every second if we should trigger an ad
|
|
172
|
+
this.periodicAdCheckInterval = setInterval(() => {
|
|
173
|
+
// Skip if ad is already playing or pending
|
|
174
|
+
if (this.isAdPlaying || this.pendingAdRequest) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const currentTime = this.video.currentTime;
|
|
179
|
+
const interval = this.config.periodicAdInterval || 30;
|
|
180
|
+
|
|
181
|
+
// Check if enough time has passed since last ad
|
|
182
|
+
if (currentTime - this.lastAdTime >= interval && currentTime > 0) {
|
|
183
|
+
console.log(`⏰ Periodic ad triggered at ${currentTime.toFixed(1)}s (interval: ${interval}s)`);
|
|
184
|
+
this.triggerPeriodicAd();
|
|
185
|
+
}
|
|
186
|
+
}, 1000); // Check every second
|
|
187
|
+
|
|
188
|
+
console.log('✅ Periodic ad scheduling enabled');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Trigger a periodic ad break
|
|
193
|
+
*/
|
|
194
|
+
private triggerPeriodicAd(): void {
|
|
195
|
+
if (this.pendingAdRequest || this.isAdPlaying) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.pendingAdRequest = true;
|
|
200
|
+
this.lastAdTime = this.video.currentTime;
|
|
201
|
+
|
|
202
|
+
console.log('🎬 Triggering periodic ad break...');
|
|
203
|
+
|
|
204
|
+
// Pause video if configured
|
|
205
|
+
if (this.config.pauseStreamDuringAd && !this.video.paused) {
|
|
206
|
+
this.video.pause();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Request a new ad
|
|
210
|
+
this.requestAds();
|
|
211
|
+
}
|
|
212
|
+
|
|
149
213
|
/**
|
|
150
214
|
* Request ads
|
|
151
215
|
*/
|
|
@@ -245,6 +309,9 @@ export class GoogleAdsManager {
|
|
|
245
309
|
|
|
246
310
|
// Start ads
|
|
247
311
|
this.adsManager.start();
|
|
312
|
+
|
|
313
|
+
// Setup periodic ad scheduling if enabled
|
|
314
|
+
this.setupPeriodicAds();
|
|
248
315
|
} catch (error) {
|
|
249
316
|
console.error('Error starting ads:', error);
|
|
250
317
|
this.video.play().catch(() => { });
|
|
@@ -263,6 +330,7 @@ export class GoogleAdsManager {
|
|
|
263
330
|
() => {
|
|
264
331
|
console.log('Ad: Content paused');
|
|
265
332
|
this.isAdPlaying = true;
|
|
333
|
+
this.pendingAdRequest = false; // Reset pending flag
|
|
266
334
|
this.video.pause();
|
|
267
335
|
|
|
268
336
|
// Force ad container visibility and z-index
|
|
@@ -289,14 +357,6 @@ export class GoogleAdsManager {
|
|
|
289
357
|
// Store cleanup function to remove listener later
|
|
290
358
|
(this.video as any).__adPlayBlocker = preventPlayDuringAd;
|
|
291
359
|
|
|
292
|
-
// ✅ Safety timeout: Force hide ad container after 120 seconds if ad gets stuck
|
|
293
|
-
this.adSafetyTimeout = setTimeout(() => {
|
|
294
|
-
if (this.isAdPlaying) {
|
|
295
|
-
console.warn('⚠️ Ad safety timeout triggered - forcing ad container to hide');
|
|
296
|
-
this.forceHideAdContainer();
|
|
297
|
-
}
|
|
298
|
-
}, 120000); // 120 seconds = 2 minutes max ad length
|
|
299
|
-
|
|
300
360
|
this.config.onAdStart?.();
|
|
301
361
|
}
|
|
302
362
|
);
|
|
@@ -308,12 +368,6 @@ export class GoogleAdsManager {
|
|
|
308
368
|
console.log('Ad: Content resume');
|
|
309
369
|
this.isAdPlaying = false;
|
|
310
370
|
|
|
311
|
-
// ✅ Clear safety timeout
|
|
312
|
-
if (this.adSafetyTimeout) {
|
|
313
|
-
clearTimeout(this.adSafetyTimeout);
|
|
314
|
-
this.adSafetyTimeout = null;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
371
|
// Remove play blocker
|
|
318
372
|
const preventPlayDuringAd = (this.video as any).__adPlayBlocker;
|
|
319
373
|
if (preventPlayDuringAd) {
|
|
@@ -321,18 +375,6 @@ export class GoogleAdsManager {
|
|
|
321
375
|
delete (this.video as any).__adPlayBlocker;
|
|
322
376
|
}
|
|
323
377
|
|
|
324
|
-
// ✅ Hide ad container to show video again
|
|
325
|
-
if (this.adContainer) {
|
|
326
|
-
this.adContainer.style.visibility = 'hidden';
|
|
327
|
-
this.adContainer.style.opacity = '0';
|
|
328
|
-
this.adContainer.style.pointerEvents = 'none';
|
|
329
|
-
this.adContainer.style.zIndex = '1';
|
|
330
|
-
console.log('✅ Ad container hidden - video visible again');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// ✅ Ensure video element is visible and playing
|
|
334
|
-
this.restoreVideoVisibility();
|
|
335
|
-
|
|
336
378
|
this.config.onAdEnd?.();
|
|
337
379
|
this.video.play().catch(() => { });
|
|
338
380
|
}
|
|
@@ -420,29 +462,11 @@ export class GoogleAdsManager {
|
|
|
420
462
|
|
|
421
463
|
this.config.onAdError?.(error);
|
|
422
464
|
|
|
423
|
-
// ✅ Clear safety timeout
|
|
424
|
-
if (this.adSafetyTimeout) {
|
|
425
|
-
clearTimeout(this.adSafetyTimeout);
|
|
426
|
-
this.adSafetyTimeout = null;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
465
|
// Destroy ads manager on error
|
|
430
466
|
if (this.adsManager) {
|
|
431
467
|
this.adsManager.destroy();
|
|
432
468
|
}
|
|
433
469
|
|
|
434
|
-
// ✅ Hide ad container on error
|
|
435
|
-
if (this.adContainer) {
|
|
436
|
-
this.adContainer.style.visibility = 'hidden';
|
|
437
|
-
this.adContainer.style.opacity = '0';
|
|
438
|
-
this.adContainer.style.pointerEvents = 'none';
|
|
439
|
-
this.adContainer.style.zIndex = '1';
|
|
440
|
-
console.log('✅ Ad container hidden after error - video visible again');
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// ✅ Restore video visibility
|
|
444
|
-
this.restoreVideoVisibility();
|
|
445
|
-
|
|
446
470
|
// Resume content playback
|
|
447
471
|
this.isAdPlaying = false;
|
|
448
472
|
this.video.play().catch(() => { });
|
|
@@ -692,99 +716,19 @@ export class GoogleAdsManager {
|
|
|
692
716
|
}
|
|
693
717
|
}
|
|
694
718
|
|
|
695
|
-
/**
|
|
696
|
-
* Restore video element visibility
|
|
697
|
-
*/
|
|
698
|
-
private restoreVideoVisibility(): void {
|
|
699
|
-
console.log('🔍 Restoring video visibility...');
|
|
700
|
-
|
|
701
|
-
// Check current video state
|
|
702
|
-
console.log('Video element state:', {
|
|
703
|
-
paused: this.video.paused,
|
|
704
|
-
currentTime: this.video.currentTime,
|
|
705
|
-
readyState: this.video.readyState,
|
|
706
|
-
networkState: this.video.networkState,
|
|
707
|
-
videoWidth: this.video.videoWidth,
|
|
708
|
-
videoHeight: this.video.videoHeight,
|
|
709
|
-
display: getComputedStyle(this.video).display,
|
|
710
|
-
visibility: getComputedStyle(this.video).visibility,
|
|
711
|
-
opacity: getComputedStyle(this.video).opacity
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// Force video element to be visible
|
|
715
|
-
this.video.style.display = 'block';
|
|
716
|
-
this.video.style.visibility = 'visible';
|
|
717
|
-
this.video.style.opacity = '1';
|
|
718
|
-
this.video.style.zIndex = '1';
|
|
719
|
-
|
|
720
|
-
// Ensure video is not hidden by any parent
|
|
721
|
-
let parent = this.video.parentElement;
|
|
722
|
-
while (parent && parent !== document.body) {
|
|
723
|
-
const styles = getComputedStyle(parent);
|
|
724
|
-
if (styles.display === 'none' || styles.visibility === 'hidden' || styles.opacity === '0') {
|
|
725
|
-
console.warn('⚠️ Parent element is hidden:', parent.className);
|
|
726
|
-
}
|
|
727
|
-
parent = parent.parentElement;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
console.log('✅ Video element visibility restored');
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Force hide ad container (safety mechanism)
|
|
735
|
-
*/
|
|
736
|
-
private forceHideAdContainer(): void {
|
|
737
|
-
console.warn('🚨 Force hiding ad container and resuming video');
|
|
738
|
-
|
|
739
|
-
this.isAdPlaying = false;
|
|
740
|
-
|
|
741
|
-
// Clear safety timeout
|
|
742
|
-
if (this.adSafetyTimeout) {
|
|
743
|
-
clearTimeout(this.adSafetyTimeout);
|
|
744
|
-
this.adSafetyTimeout = null;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// Remove play blocker
|
|
748
|
-
const preventPlayDuringAd = (this.video as any).__adPlayBlocker;
|
|
749
|
-
if (preventPlayDuringAd) {
|
|
750
|
-
this.video.removeEventListener('play', preventPlayDuringAd);
|
|
751
|
-
delete (this.video as any).__adPlayBlocker;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Hide ad container
|
|
755
|
-
if (this.adContainer) {
|
|
756
|
-
this.adContainer.style.visibility = 'hidden';
|
|
757
|
-
this.adContainer.style.opacity = '0';
|
|
758
|
-
this.adContainer.style.pointerEvents = 'none';
|
|
759
|
-
this.adContainer.style.zIndex = '1';
|
|
760
|
-
console.log('✅ Ad container force-hidden');
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Destroy stuck ads manager
|
|
764
|
-
if (this.adsManager) {
|
|
765
|
-
try {
|
|
766
|
-
this.adsManager.destroy();
|
|
767
|
-
} catch (e) {
|
|
768
|
-
console.warn('Failed to destroy ads manager:', e);
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// ✅ Restore video visibility
|
|
773
|
-
this.restoreVideoVisibility();
|
|
774
|
-
|
|
775
|
-
// Resume video
|
|
776
|
-
this.video.play().catch(() => { });
|
|
777
|
-
|
|
778
|
-
// Notify callback
|
|
779
|
-
this.config.onAdEnd?.();
|
|
780
|
-
}
|
|
781
|
-
|
|
782
719
|
/**
|
|
783
720
|
* Cleanup
|
|
784
721
|
*/
|
|
785
722
|
destroy(): void {
|
|
786
723
|
this.hideUnmuteButton();
|
|
787
724
|
|
|
725
|
+
// Clear periodic ad interval
|
|
726
|
+
if (this.periodicAdCheckInterval) {
|
|
727
|
+
clearInterval(this.periodicAdCheckInterval);
|
|
728
|
+
this.periodicAdCheckInterval = null;
|
|
729
|
+
console.log('✅ Periodic ad scheduling stopped');
|
|
730
|
+
}
|
|
731
|
+
|
|
788
732
|
if (this.adsManager) {
|
|
789
733
|
this.adsManager.destroy();
|
|
790
734
|
this.adsManager = null;
|
|
@@ -796,5 +740,6 @@ export class GoogleAdsManager {
|
|
|
796
740
|
}
|
|
797
741
|
|
|
798
742
|
this.isAdPlaying = false;
|
|
743
|
+
this.pendingAdRequest = false;
|
|
799
744
|
}
|
|
800
745
|
}
|
|
@@ -4,7 +4,6 @@ import type { CSSProperties } from 'react';
|
|
|
4
4
|
import type { VideoSource, SubtitleTrack, VideoMetadata, PlayerConfig } from '../../core/dist';
|
|
5
5
|
import { WebPlayer } from '../WebPlayer';
|
|
6
6
|
import { GoogleAdsManager } from '../ads/GoogleAdsManager';
|
|
7
|
-
import { LiveStreamAdsManager } from '../ads/LiveStreamAdsManager';
|
|
8
7
|
// EPG imports - conditionally loaded
|
|
9
8
|
import type { EPGData, EPGConfig, EPGProgram, EPGProgramRow } from './types/EPGTypes';
|
|
10
9
|
import type { VCManifest, VCProduct, VCEvent } from './types/VideoCommerceTypes';
|
|
@@ -407,34 +406,24 @@ export type WebPlayerViewProps = {
|
|
|
407
406
|
// Google Ads Configuration
|
|
408
407
|
googleAds?: {
|
|
409
408
|
adTagUrl: string; // VAST/VMAP ad tag URL
|
|
410
|
-
midrollTimes?: number[]; // Mid-roll ad times in seconds [30, 60, 120]
|
|
409
|
+
midrollTimes?: number[]; // Mid-roll ad times in seconds [30, 60, 120]
|
|
410
|
+
|
|
411
|
+
// Periodic ad breaks (for live streams or automatic scheduling)
|
|
412
|
+
liveAdBreakMode?: 'periodic' | 'manual'; // 'periodic' = auto-schedule at intervals, 'manual' = use midrollTimes
|
|
413
|
+
periodicAdInterval?: number; // Interval in seconds between ads (e.g., 30 = ad every 30 seconds)
|
|
414
|
+
syncToLiveEdge?: boolean; // For live streams: sync back to live edge after ad
|
|
415
|
+
pauseStreamDuringAd?: boolean; // Pause underlying stream during ad playback
|
|
416
|
+
liveEdgeOffset?: number; // Seconds behind live edge to resume at (default: 3)
|
|
417
|
+
|
|
411
418
|
companionAdSlots?: Array<{ // Companion ad containers
|
|
412
419
|
containerId: string;
|
|
413
420
|
width: number;
|
|
414
421
|
height: number;
|
|
415
422
|
}>;
|
|
416
|
-
|
|
417
|
-
// Live Stream Ad Options (NEW)
|
|
418
|
-
liveAdBreakMode?: 'metadata' | 'periodic' | 'manual' | 'hybrid'; // Live ad insertion mode
|
|
419
|
-
periodicAdInterval?: number; // For periodic mode: ad every X seconds (default: 300)
|
|
420
|
-
syncToLiveEdge?: boolean; // Jump to live edge after ad (default: false - DVR style)
|
|
421
|
-
liveEdgeOffset?: number; // Seconds behind live edge (default: 3)
|
|
422
|
-
pauseStreamDuringAd?: boolean; // Pause stream download during ad (default: true)
|
|
423
|
-
|
|
424
|
-
// Metadata Detection Config (for 'metadata' mode)
|
|
425
|
-
metadataConfig?: {
|
|
426
|
-
detectDateRange?: boolean; // Detect #EXT-X-DATERANGE tags (default: true)
|
|
427
|
-
detectID3?: boolean; // Detect ID3 timed metadata (default: true)
|
|
428
|
-
adClassNames?: string[]; // CLASS values to detect (default: ['com.google.ads', 'ads'])
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Callbacks
|
|
432
423
|
onAdStart?: () => void; // Called when ad starts
|
|
433
424
|
onAdEnd?: () => void; // Called when ad ends
|
|
434
425
|
onAdError?: (error: any) => void; // Called on ad error
|
|
435
426
|
onAllAdsComplete?: () => void; // Called when all ads complete
|
|
436
|
-
onLiveAdBreakDetected?: (metadata: any) => void; // Called when live ad cue detected (metadata mode)
|
|
437
|
-
onAdBreakScheduled?: (scheduledTime: number) => void; // Called when ad scheduled (periodic mode)
|
|
438
427
|
};
|
|
439
428
|
|
|
440
429
|
// Chapter Event Callbacks
|
|
@@ -1244,25 +1233,9 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1244
1233
|
|
|
1245
1234
|
// Initialize Google Ads if configured
|
|
1246
1235
|
if (props.googleAds) {
|
|
1247
|
-
//
|
|
1248
|
-
const waitForAdContainer = () => {
|
|
1249
|
-
return new Promise<void>((resolve) => {
|
|
1250
|
-
const checkContainer = () => {
|
|
1251
|
-
if (adContainerRef.current && document.body.contains(adContainerRef.current)) {
|
|
1252
|
-
resolve();
|
|
1253
|
-
} else {
|
|
1254
|
-
setTimeout(checkContainer, 50);
|
|
1255
|
-
}
|
|
1256
|
-
};
|
|
1257
|
-
checkContainer();
|
|
1258
|
-
});
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1236
|
+
// Small delay to ensure ad container is properly mounted in DOM
|
|
1261
1237
|
setTimeout(async () => {
|
|
1262
1238
|
try {
|
|
1263
|
-
// Wait for ad container to be in DOM
|
|
1264
|
-
await waitForAdContainer();
|
|
1265
|
-
|
|
1266
1239
|
// Ensure ad container exists
|
|
1267
1240
|
if (!adContainerRef.current) {
|
|
1268
1241
|
console.error('❌ Ad container ref is null - cannot initialize ads');
|
|
@@ -1305,122 +1278,22 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1305
1278
|
return true;
|
|
1306
1279
|
};
|
|
1307
1280
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
periodicAdInterval: props.googleAds.periodicAdInterval,
|
|
1325
|
-
syncToLiveEdge: props.googleAds.syncToLiveEdge,
|
|
1326
|
-
liveEdgeOffset: props.googleAds.liveEdgeOffset,
|
|
1327
|
-
pauseStreamDuringAd: props.googleAds.pauseStreamDuringAd,
|
|
1328
|
-
metadataConfig: props.googleAds.metadataConfig,
|
|
1329
|
-
|
|
1330
|
-
onAdStart: () => {
|
|
1331
|
-
setIsAdPlaying(true);
|
|
1332
|
-
|
|
1333
|
-
// Check if this is a pre-roll ad (video time near 0)
|
|
1334
|
-
if (videoElement.currentTime < 1) {
|
|
1335
|
-
console.log('🎬 Pre-roll ad starting - blocking video playback');
|
|
1336
|
-
isPrerollPlaying = true;
|
|
1337
|
-
hasPrerollAd = true;
|
|
1338
|
-
|
|
1339
|
-
// Add playback blocker
|
|
1340
|
-
videoElement.addEventListener('play', blockVideoUntilPreroll);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// Notify player to block keyboard controls
|
|
1344
|
-
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1345
|
-
(player as any).setAdPlaying(true);
|
|
1346
|
-
}
|
|
1347
|
-
props.googleAds?.onAdStart?.();
|
|
1348
|
-
},
|
|
1349
|
-
onLiveAdBreakDetected: props.googleAds.onLiveAdBreakDetected,
|
|
1350
|
-
onAdBreakScheduled: props.googleAds.onAdBreakScheduled,
|
|
1351
|
-
onAdEnd: () => {
|
|
1352
|
-
setIsAdPlaying(false);
|
|
1353
|
-
|
|
1354
|
-
// Handle pre-roll completion
|
|
1355
|
-
if (isPrerollPlaying) {
|
|
1356
|
-
console.log('🎬 Pre-roll ad completed');
|
|
1357
|
-
isPrerollPlaying = false;
|
|
1358
|
-
|
|
1359
|
-
// Remove playback blocker
|
|
1360
|
-
videoElement.removeEventListener('play', blockVideoUntilPreroll);
|
|
1361
|
-
|
|
1362
|
-
// Reset video to beginning if it leaked any playback
|
|
1363
|
-
if (videoElement.currentTime > 0 && videoElement.currentTime < 10) {
|
|
1364
|
-
console.log(`⏮️ Resetting video to 0:00 (was at ${videoElement.currentTime.toFixed(2)}s)`);
|
|
1365
|
-
videoElement.currentTime = 0;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// Notify player to unblock keyboard controls
|
|
1370
|
-
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1371
|
-
(player as any).setAdPlaying(false);
|
|
1372
|
-
}
|
|
1373
|
-
props.googleAds?.onAdEnd?.();
|
|
1374
|
-
},
|
|
1375
|
-
onAdError: (error) => {
|
|
1376
|
-
setIsAdPlaying(false);
|
|
1377
|
-
isPrerollPlaying = false;
|
|
1378
|
-
|
|
1379
|
-
// Remove blocker on error
|
|
1380
|
-
videoElement.removeEventListener('play', blockVideoUntilPreroll);
|
|
1381
|
-
|
|
1382
|
-
// Notify player to unblock keyboard controls
|
|
1383
|
-
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1384
|
-
(player as any).setAdPlaying(false);
|
|
1385
|
-
}
|
|
1386
|
-
props.googleAds?.onAdError?.(error);
|
|
1387
|
-
},
|
|
1388
|
-
onAllAdsComplete: () => {
|
|
1389
|
-
setIsAdPlaying(false);
|
|
1390
|
-
isPrerollPlaying = false;
|
|
1391
|
-
|
|
1392
|
-
// Remove blocker when all ads complete
|
|
1393
|
-
videoElement.removeEventListener('play', blockVideoUntilPreroll);
|
|
1394
|
-
|
|
1395
|
-
// Notify player to unblock keyboard controls
|
|
1396
|
-
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1397
|
-
(player as any).setAdPlaying(false);
|
|
1398
|
-
}
|
|
1399
|
-
props.googleAds?.onAllAdsComplete?.();
|
|
1400
|
-
},
|
|
1401
|
-
onAdCuePoints: (cuePoints: number[]) => {
|
|
1402
|
-
// Check if there's a pre-roll (cue point at 0)
|
|
1403
|
-
if (cuePoints.includes(0)) {
|
|
1404
|
-
console.log('✅ Pre-roll ad detected in cue points');
|
|
1405
|
-
hasPrerollAd = true;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
// Inject markers from VMAP cue points (if midrollTimes not provided)
|
|
1409
|
-
if (!props.googleAds?.midrollTimes || props.googleAds.midrollTimes.length === 0) {
|
|
1410
|
-
console.log('🔵 Using VMAP cue points for ad markers:', cuePoints);
|
|
1411
|
-
injectAdMarkersFromTimes(cuePoints);
|
|
1412
|
-
}
|
|
1413
|
-
},
|
|
1414
|
-
}
|
|
1415
|
-
)
|
|
1416
|
-
: new GoogleAdsManager(
|
|
1417
|
-
videoElement,
|
|
1418
|
-
adContainer,
|
|
1419
|
-
{
|
|
1420
|
-
adTagUrl: props.googleAds.adTagUrl,
|
|
1421
|
-
midrollTimes: props.googleAds.midrollTimes,
|
|
1422
|
-
companionAdSlots: props.googleAds.companionAdSlots,
|
|
1423
|
-
onAdStart: () => {
|
|
1281
|
+
const adsManager = new GoogleAdsManager(
|
|
1282
|
+
videoElement,
|
|
1283
|
+
adContainer,
|
|
1284
|
+
{
|
|
1285
|
+
adTagUrl: props.googleAds.adTagUrl,
|
|
1286
|
+
midrollTimes: props.googleAds.midrollTimes,
|
|
1287
|
+
companionAdSlots: props.googleAds.companionAdSlots,
|
|
1288
|
+
|
|
1289
|
+
// Periodic ad configuration
|
|
1290
|
+
liveAdBreakMode: props.googleAds.liveAdBreakMode,
|
|
1291
|
+
periodicAdInterval: props.googleAds.periodicAdInterval,
|
|
1292
|
+
syncToLiveEdge: props.googleAds.syncToLiveEdge,
|
|
1293
|
+
pauseStreamDuringAd: props.googleAds.pauseStreamDuringAd,
|
|
1294
|
+
liveEdgeOffset: props.googleAds.liveEdgeOffset,
|
|
1295
|
+
|
|
1296
|
+
onAdStart: () => {
|
|
1424
1297
|
setIsAdPlaying(true);
|
|
1425
1298
|
|
|
1426
1299
|
// Check if this is a pre-roll ad (video time near 0)
|
|
@@ -1504,21 +1377,12 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1504
1377
|
},
|
|
1505
1378
|
}
|
|
1506
1379
|
);
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
if (isLiveAdMode) {
|
|
1510
|
-
// For LiveStreamAdsManager, pass HLS instance for metadata detection
|
|
1511
|
-
const hlsInstance = (player as any).hls || (player as any).getHlsInstance?.();
|
|
1512
|
-
await (adsManager as LiveStreamAdsManager).initializeLiveAds(hlsInstance);
|
|
1513
|
-
console.log('✅ Live Stream Ads initialized successfully');
|
|
1514
|
-
} else {
|
|
1515
|
-
// For regular GoogleAdsManager
|
|
1516
|
-
await adsManager.initialize();
|
|
1517
|
-
console.log('✅ Google Ads initialized successfully');
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1380
|
+
|
|
1381
|
+
await adsManager.initialize();
|
|
1520
1382
|
adsManagerRef.current = adsManager;
|
|
1521
1383
|
|
|
1384
|
+
console.log('✅ Google Ads initialized successfully');
|
|
1385
|
+
|
|
1522
1386
|
// Move ad container into player wrapper for fullscreen support
|
|
1523
1387
|
const playerWrapper = containerRef.current?.querySelector('.uvf-player-wrapper');
|
|
1524
1388
|
if (playerWrapper && adContainerRef.current && adContainerRef.current.parentElement) {
|