unified-video-framework 1.4.371 → 1.4.373

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.
@@ -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,17 +150,17 @@ 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 => {
@@ -169,7 +169,7 @@ export class GoogleAdsManager {
169
169
  adsRequest.setAdWillAutoPlay(true);
170
170
  adsRequest.setAdWillPlayMuted(false);
171
171
  }
172
-
172
+
173
173
  // Request ads
174
174
  this.adsLoader.requestAds(adsRequest);
175
175
  } catch (error) {
@@ -194,16 +194,16 @@ export class GoogleAdsManager {
194
194
  */
195
195
  private onAdsManagerLoaded(event: any): void {
196
196
  const google = (window as any).google;
197
-
197
+
198
198
  // Setup ads rendering settings
199
199
  const adsRenderingSettings = new google.ima.AdsRenderingSettings();
200
200
  adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
201
201
  adsRenderingSettings.enablePreloading = true;
202
202
  adsRenderingSettings.mute = false; // ✅ Enable audio for ads
203
-
203
+
204
204
  // Get the ads manager
205
205
  this.adsManager = event.getAdsManager(this.video, adsRenderingSettings);
206
-
206
+
207
207
  // Extract cue points (ad break times) from VMAP/ad server
208
208
  try {
209
209
  const cuePoints = this.adsManager.getCuePoints();
@@ -215,9 +215,9 @@ export class GoogleAdsManager {
215
215
  if (time === -1) return -1; // Post-roll (will be converted to video duration later)
216
216
  return time; // Mid-roll at specific time
217
217
  });
218
-
218
+
219
219
  console.log('📍 Ad cue points detected (pre/mid/post):', allCuePoints);
220
-
220
+
221
221
  // Notify callback with all cue points
222
222
  if (this.config.onAdCuePoints) {
223
223
  this.config.onAdCuePoints(allCuePoints);
@@ -226,10 +226,10 @@ export class GoogleAdsManager {
226
226
  } catch (error) {
227
227
  console.warn('Could not extract ad cue points:', error);
228
228
  }
229
-
229
+
230
230
  // Setup ads manager event listeners
231
231
  this.setupAdsManagerListeners();
232
-
232
+
233
233
  try {
234
234
  // Initialize ads manager
235
235
  this.adsManager.init(
@@ -237,12 +237,12 @@ export class GoogleAdsManager {
237
237
  this.video.clientHeight,
238
238
  google.ima.ViewMode.NORMAL
239
239
  );
240
-
240
+
241
241
  // Start ads
242
242
  this.adsManager.start();
243
243
  } catch (error) {
244
244
  console.error('Error starting ads:', error);
245
- this.video.play().catch(() => {});
245
+ this.video.play().catch(() => { });
246
246
  }
247
247
  }
248
248
 
@@ -251,7 +251,7 @@ export class GoogleAdsManager {
251
251
  */
252
252
  private setupAdsManagerListeners(): void {
253
253
  const google = (window as any).google;
254
-
254
+
255
255
  // Content pause - ad is about to play
256
256
  this.adsManager.addEventListener(
257
257
  google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
@@ -259,21 +259,45 @@ export class GoogleAdsManager {
259
259
  console.log('Ad: Content paused');
260
260
  this.isAdPlaying = true;
261
261
  this.video.pause();
262
+
263
+ // Strict enforcement: Prevent video from playing during ads
264
+ const preventPlayDuringAd = (e: Event) => {
265
+ if (this.isAdPlaying) {
266
+ e.preventDefault();
267
+ this.video.pause();
268
+ console.warn('Blocked video play attempt during ad');
269
+ }
270
+ };
271
+
272
+ // Add play event listener to block any play attempts
273
+ this.video.addEventListener('play', preventPlayDuringAd);
274
+
275
+ // Store cleanup function to remove listener later
276
+ (this.video as any).__adPlayBlocker = preventPlayDuringAd;
277
+
262
278
  this.config.onAdStart?.();
263
279
  }
264
280
  );
265
-
281
+
266
282
  // Content resume - ad finished
267
283
  this.adsManager.addEventListener(
268
284
  google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
269
285
  () => {
270
286
  console.log('Ad: Content resume');
271
287
  this.isAdPlaying = false;
288
+
289
+ // Remove play blocker
290
+ const preventPlayDuringAd = (this.video as any).__adPlayBlocker;
291
+ if (preventPlayDuringAd) {
292
+ this.video.removeEventListener('play', preventPlayDuringAd);
293
+ delete (this.video as any).__adPlayBlocker;
294
+ }
295
+
272
296
  this.config.onAdEnd?.();
273
- this.video.play().catch(() => {});
297
+ this.video.play().catch(() => { });
274
298
  }
275
299
  );
276
-
300
+
277
301
  // Ad started
278
302
  this.adsManager.addEventListener(
279
303
  google.ima.AdEvent.Type.STARTED,
@@ -285,19 +309,19 @@ export class GoogleAdsManager {
285
309
  skippable: ad.getSkipTimeOffset() !== -1,
286
310
  title: ad.getTitle(),
287
311
  });
288
-
312
+
289
313
  // Sync ads mute state with video element
290
314
  this.isMuted = this.video.muted;
291
315
  console.log(`Ad started - video.muted=${this.video.muted}, isMuted=${this.isMuted}`);
292
-
316
+
293
317
  // Check if video is actually muted and show unmute button only then
294
318
  if (this.isMuted) {
295
319
  this.showUnmuteButton();
296
320
  }
297
-
321
+
298
322
  }
299
323
  );
300
-
324
+
301
325
  // Ad completed
302
326
  this.adsManager.addEventListener(
303
327
  google.ima.AdEvent.Type.COMPLETE,
@@ -305,7 +329,7 @@ export class GoogleAdsManager {
305
329
  console.log('Ad completed');
306
330
  }
307
331
  );
308
-
332
+
309
333
  // All ads completed
310
334
  this.adsManager.addEventListener(
311
335
  google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
@@ -314,13 +338,13 @@ export class GoogleAdsManager {
314
338
  this.config.onAllAdsComplete?.();
315
339
  }
316
340
  );
317
-
341
+
318
342
  // Ad error
319
343
  this.adsManager.addEventListener(
320
344
  google.ima.AdErrorEvent.Type.AD_ERROR,
321
345
  (event: any) => this.onAdError(event)
322
346
  );
323
-
347
+
324
348
  // Ad skipped
325
349
  this.adsManager.addEventListener(
326
350
  google.ima.AdEvent.Type.SKIPPED,
@@ -328,7 +352,7 @@ export class GoogleAdsManager {
328
352
  console.log('Ad skipped by user');
329
353
  }
330
354
  );
331
-
355
+
332
356
  // Ad paused (by click-through)
333
357
  this.adsManager.addEventListener(
334
358
  google.ima.AdEvent.Type.PAUSED,
@@ -337,7 +361,7 @@ export class GoogleAdsManager {
337
361
  // Don't mark as not playing - keep isAdPlaying true so we can resume
338
362
  }
339
363
  );
340
-
364
+
341
365
  // Ad playing (resume after pause)
342
366
  this.adsManager.addEventListener(
343
367
  google.ima.AdEvent.Type.PLAYING,
@@ -353,17 +377,17 @@ export class GoogleAdsManager {
353
377
  private onAdError(event: any): void {
354
378
  const error = event.getError?.();
355
379
  console.error('Ad error:', error?.getMessage?.() || error);
356
-
380
+
357
381
  this.config.onAdError?.(error);
358
-
382
+
359
383
  // Destroy ads manager on error
360
384
  if (this.adsManager) {
361
385
  this.adsManager.destroy();
362
386
  }
363
-
387
+
364
388
  // Resume content playback
365
389
  this.isAdPlaying = false;
366
- this.video.play().catch(() => {});
390
+ this.video.play().catch(() => { });
367
391
  }
368
392
 
369
393
  /**
@@ -396,10 +420,12 @@ export class GoogleAdsManager {
396
420
  /**
397
421
  * Resize ads
398
422
  */
399
- resize(width: number, height: number): void {
423
+ resize(width: number, height: number, viewMode?: any): void {
400
424
  const google = (window as any).google;
401
- if (this.adsManager) {
402
- this.adsManager.resize(width, height, google.ima.ViewMode.NORMAL);
425
+ if (this.adsManager && google && google.ima) {
426
+ // Use provided viewMode or default to NORMAL
427
+ const mode = viewMode || google.ima.ViewMode.NORMAL;
428
+ this.adsManager.resize(width, height, mode);
403
429
  }
404
430
  }
405
431
 
@@ -427,7 +453,7 @@ export class GoogleAdsManager {
427
453
  if (this.unmuteButton) {
428
454
  this.unmuteButton.remove();
429
455
  }
430
-
456
+
431
457
  // Create unmute button (matching WebPlayer.ts style)
432
458
  this.unmuteButton = document.createElement('button');
433
459
  this.unmuteButton.id = 'ad-unmute-btn';
@@ -439,13 +465,13 @@ export class GoogleAdsManager {
439
465
  </svg>
440
466
  <span class="uvf-unmute-text">Tap to unmute</span>
441
467
  `;
442
-
468
+
443
469
  // Click handler to unmute
444
470
  this.unmuteButton.addEventListener('click', (e) => {
445
471
  e.stopPropagation();
446
472
  this.toggleAdMute();
447
473
  });
448
-
474
+
449
475
  // Add styles if not already added
450
476
  if (!document.getElementById('uvf-unmute-styles')) {
451
477
  const style = document.createElement('style');
@@ -517,33 +543,33 @@ export class GoogleAdsManager {
517
543
  `;
518
544
  document.head.appendChild(style);
519
545
  }
520
-
546
+
521
547
  // Add to ad container
522
548
  this.adContainer.appendChild(this.unmuteButton);
523
549
  console.log('Unmute button displayed (matching player style)');
524
550
  }
525
-
551
+
526
552
  /**
527
553
  * Toggle ad mute state
528
554
  */
529
555
  private toggleAdMute(): void {
530
556
  this.isMuted = !this.isMuted;
531
-
557
+
532
558
  if (this.adsManager) {
533
559
  this.adsManager.setVolume(this.isMuted ? 0 : 1);
534
560
  console.log(`Ad ${this.isMuted ? 'muted' : 'unmuted'}`);
535
561
  }
536
-
562
+
537
563
  // Sync mute state with video element
538
564
  this.video.muted = this.isMuted;
539
-
565
+
540
566
  // Hide button if unmuted
541
567
  if (!this.isMuted && this.unmuteButton) {
542
568
  this.unmuteButton.remove();
543
569
  this.unmuteButton = null;
544
570
  }
545
571
  }
546
-
572
+
547
573
  /**
548
574
  * Resume ad playback after click-through
549
575
  */
@@ -553,9 +579,9 @@ export class GoogleAdsManager {
553
579
  if (!this.adsManager || !this.isAdPlaying) {
554
580
  return;
555
581
  }
556
-
582
+
557
583
  console.log('Attempting to resume ad playback...');
558
-
584
+
559
585
  // Try multiple resume methods
560
586
  try {
561
587
  this.adsManager.resume();
@@ -563,7 +589,7 @@ export class GoogleAdsManager {
563
589
  } catch (e) {
564
590
  console.warn('resume() failed:', e);
565
591
  }
566
-
592
+
567
593
  // Also try playing the video element directly
568
594
  if (this.video) {
569
595
  try {
@@ -584,7 +610,7 @@ export class GoogleAdsManager {
584
610
  console.error('Error resuming ad playback:', error);
585
611
  }
586
612
  }
587
-
613
+
588
614
  /**
589
615
  * Hide unmute button
590
616
  */
@@ -600,17 +626,17 @@ export class GoogleAdsManager {
600
626
  */
601
627
  destroy(): void {
602
628
  this.hideUnmuteButton();
603
-
629
+
604
630
  if (this.adsManager) {
605
631
  this.adsManager.destroy();
606
632
  this.adsManager = null;
607
633
  }
608
-
634
+
609
635
  if (this.adsLoader) {
610
636
  this.adsLoader.destroy();
611
637
  this.adsLoader = null;
612
638
  }
613
-
639
+
614
640
  this.isAdPlaying = false;
615
641
  }
616
642
  }
@@ -417,7 +417,7 @@ export class CreditsButtonController {
417
417
  backgroundColor: 'rgba(255, 255, 255, 0.9)',
418
418
  width: '0%',
419
419
  transition: 'width 1s linear',
420
- borderRadius: '0 0 6px 6px'
420
+ borderRadius: '6px'
421
421
  });
422
422
 
423
423
  return progressBar;
@@ -1166,7 +1166,29 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1166
1166
  (player as any).on('onBuffering', props.onBuffering);
1167
1167
  }
1168
1168
  if (props.onFullscreenChange && typeof (player as any).on === 'function') {
1169
- (player as any).on('onFullscreenChanged', props.onFullscreenChange);
1169
+ (player as any).on('onFullscreenChanged', (isFullscreen: boolean) => {
1170
+ // Resize Google Ads if active
1171
+ if (adsManagerRef.current) {
1172
+ const google = (window as any).google;
1173
+ if (google && google.ima) {
1174
+ const container = containerRef.current;
1175
+ const width = isFullscreen
1176
+ ? window.screen.width
1177
+ : (container?.clientWidth || window.innerWidth);
1178
+ const height = isFullscreen
1179
+ ? window.screen.height
1180
+ : (container?.clientHeight || window.innerHeight);
1181
+ const viewMode = isFullscreen
1182
+ ? google.ima.ViewMode.FULLSCREEN
1183
+ : google.ima.ViewMode.NORMAL;
1184
+
1185
+ adsManagerRef.current.resize(width, height, viewMode);
1186
+ }
1187
+ }
1188
+
1189
+ // Call user's fullscreen callback
1190
+ props.onFullscreenChange?.(isFullscreen);
1191
+ });
1170
1192
  }
1171
1193
  if (props.onPictureInPictureChange && typeof (player as any).on === 'function') {
1172
1194
  (player as any).on('onPictureInPicturechange', props.onPictureInPictureChange);