unified-video-framework 1.4.405 β†’ 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.
@@ -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,26 +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] (VOD only)
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 Options
418
- liveAdBreakMode?: 'periodic' | 'manual'; // Live stream ad mode
419
- periodicAdInterval?: number; // Seconds between ads (default: 300)
420
- syncToLiveEdge?: boolean; // Jump to live edge after ad (default: false)
421
- liveEdgeOffset?: number; // Seconds behind live edge (default: 3)
422
- pauseStreamDuringAd?: boolean; // Pause stream during ad (default: true)
423
-
424
- // Callbacks
425
423
  onAdStart?: () => void; // Called when ad starts
426
424
  onAdEnd?: () => void; // Called when ad ends
427
425
  onAdError?: (error: any) => void; // Called on ad error
428
426
  onAllAdsComplete?: () => void; // Called when all ads complete
429
- onAdBreakScheduled?: (scheduledTime: number) => void; // Called when ad is scheduled (periodic mode)
430
427
  };
431
428
 
432
429
  // Chapter Event Callbacks
@@ -1281,82 +1278,22 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1281
1278
  return true;
1282
1279
  };
1283
1280
 
1284
- // Determine if this is a live stream with live ad modes
1285
- const isLiveAdMode = props.googleAds.liveAdBreakMode &&
1286
- props.googleAds.liveAdBreakMode !== 'manual';
1287
-
1288
- // Use LiveStreamAdsManager for live ad modes, otherwise use GoogleAdsManager
1289
- const adsManager = isLiveAdMode
1290
- ? new LiveStreamAdsManager(
1291
- videoElement,
1292
- adContainer,
1293
- {
1294
- adTagUrl: props.googleAds.adTagUrl,
1295
- liveAdBreakMode: props.googleAds.liveAdBreakMode,
1296
- periodicAdInterval: props.googleAds.periodicAdInterval,
1297
- syncToLiveEdge: props.googleAds.syncToLiveEdge,
1298
- liveEdgeOffset: props.googleAds.liveEdgeOffset,
1299
- pauseStreamDuringAd: props.googleAds.pauseStreamDuringAd,
1300
- onAdStart: () => {
1301
- setIsAdPlaying(true);
1302
-
1303
- // Check if this is a pre-roll ad
1304
- if (videoElement.currentTime < 1) {
1305
- console.log('🎬 Pre-roll ad starting');
1306
- isPrerollPlaying = true;
1307
- hasPrerollAd = true;
1308
- videoElement.addEventListener('play', blockVideoUntilPreroll);
1309
- }
1310
-
1311
- if (typeof (player as any).setAdPlaying === 'function') {
1312
- (player as any).setAdPlaying(true);
1313
- }
1314
- props.googleAds?.onAdStart?.();
1315
- },
1316
- onAdBreakScheduled: props.googleAds.onAdBreakScheduled,
1317
- onAdEnd: () => {
1318
- setIsAdPlaying(false);
1319
-
1320
- if (isPrerollPlaying) {
1321
- console.log('βœ… Pre-roll ad completed - unblocking video');
1322
- isPrerollPlaying = false;
1323
- videoElement.removeEventListener('play', blockVideoUntilPreroll);
1324
- videoElement.play().catch(() => {});
1325
- }
1326
-
1327
- if (typeof (player as any).setAdPlaying === 'function') {
1328
- (player as any).setAdPlaying(false);
1329
- }
1330
- props.googleAds?.onAdEnd?.();
1331
- },
1332
- onAdError: (error) => {
1333
- setIsAdPlaying(false);
1334
- isPrerollPlaying = false;
1335
- videoElement.removeEventListener('play', blockVideoUntilPreroll);
1336
- if (typeof (player as any).setAdPlaying === 'function') {
1337
- (player as any).setAdPlaying(false);
1338
- }
1339
- props.googleAds?.onAdError?.(error);
1340
- },
1341
- onAllAdsComplete: () => {
1342
- setIsAdPlaying(false);
1343
- isPrerollPlaying = false;
1344
- videoElement.removeEventListener('play', blockVideoUntilPreroll);
1345
- if (typeof (player as any).setAdPlaying === 'function') {
1346
- (player as any).setAdPlaying(false);
1347
- }
1348
- props.googleAds?.onAllAdsComplete?.();
1349
- },
1350
- }
1351
- )
1352
- : new GoogleAdsManager(
1353
- videoElement,
1354
- adContainer,
1355
- {
1356
- adTagUrl: props.googleAds.adTagUrl,
1357
- midrollTimes: props.googleAds.midrollTimes,
1358
- companionAdSlots: props.googleAds.companionAdSlots,
1359
- 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: () => {
1360
1297
  setIsAdPlaying(true);
1361
1298
 
1362
1299
  // Check if this is a pre-roll ad (video time near 0)
@@ -1440,21 +1377,12 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1440
1377
  },
1441
1378
  }
1442
1379
  );
1443
-
1444
- // Initialize ads manager (different for live vs VOD)
1445
- if (isLiveAdMode) {
1446
- // For LiveStreamAdsManager, pass HLS instance for live edge detection
1447
- const hlsInstance = (player as any).hls || (player as any).getHlsInstance?.();
1448
- await (adsManager as LiveStreamAdsManager).initializeLiveAds(hlsInstance);
1449
- console.log('βœ… Live Stream Ads initialized successfully');
1450
- } else {
1451
- // For regular GoogleAdsManager
1452
- await adsManager.initialize();
1453
- console.log('βœ… Google Ads initialized successfully');
1454
- }
1455
-
1380
+
1381
+ await adsManager.initialize();
1456
1382
  adsManagerRef.current = adsManager;
1457
1383
 
1384
+ console.log('βœ… Google Ads initialized successfully');
1385
+
1458
1386
  // Move ad container into player wrapper for fullscreen support
1459
1387
  const playerWrapper = containerRef.current?.querySelector('.uvf-player-wrapper');
1460
1388
  if (playerWrapper && adContainerRef.current && adContainerRef.current.parentElement) {
@@ -1,255 +0,0 @@
1
- /**
2
- * Live Stream Ads Manager
3
- * Extends GoogleAdsManager with periodic ad break support for live streams
4
- */
5
-
6
- import { GoogleAdsManager, GoogleAdsConfig } from './GoogleAdsManager';
7
-
8
- export interface LiveStreamAdsConfig extends GoogleAdsConfig {
9
- liveAdBreakMode?: 'periodic' | 'manual';
10
- periodicAdInterval?: number; // Seconds between ads (default: 300)
11
- syncToLiveEdge?: boolean; // Jump to live edge after ad (default: false)
12
- liveEdgeOffset?: number; // Seconds behind live edge (default: 3)
13
- pauseStreamDuringAd?: boolean; // Pause stream download during ads (default: true)
14
-
15
- // Callbacks
16
- onAdBreakScheduled?: (scheduledTime: number) => void;
17
- }
18
-
19
- export class LiveStreamAdsManager extends GoogleAdsManager {
20
- private liveConfig: LiveStreamAdsConfig;
21
- private isLiveStream = false;
22
- private periodicAdTimer: any = null;
23
- private lastAdBreakTime = 0;
24
- private playbackStartTime = 0;
25
- private streamStartTime = 0;
26
- private hlsInstance: any = null;
27
-
28
- constructor(video: HTMLVideoElement, adContainer: HTMLElement, config: LiveStreamAdsConfig) {
29
- super(video, adContainer, config);
30
- this.liveConfig = {
31
- periodicAdInterval: 300,
32
- syncToLiveEdge: false,
33
- liveEdgeOffset: 3,
34
- pauseStreamDuringAd: true,
35
- ...config
36
- };
37
- }
38
-
39
- /**
40
- * Initialize live stream ads with periodic mode
41
- */
42
- async initializeLiveAds(hlsInstance?: any): Promise<void> {
43
- // Initialize parent (Google IMA SDK)
44
- await this.initialize();
45
-
46
- this.isLiveStream = true;
47
- this.hlsInstance = hlsInstance;
48
- this.playbackStartTime = this.video.currentTime || 0;
49
- this.streamStartTime = Date.now();
50
-
51
- const mode = this.liveConfig.liveAdBreakMode || 'periodic';
52
-
53
- console.log(`πŸ”΄ Live Stream Ads initialized`);
54
- console.log(` Mode: ${mode}`);
55
- console.log(` Periodic interval: ${this.liveConfig.periodicAdInterval}s`);
56
-
57
- if (mode === 'periodic') {
58
- this.setupPeriodicAdBreaks();
59
- } else {
60
- console.log('Manual mode - call triggerAdBreak() to show ads');
61
- }
62
- }
63
-
64
- /**
65
- * Setup periodic timer-based ad breaks
66
- */
67
- private setupPeriodicAdBreaks(): void {
68
- const interval = this.liveConfig.periodicAdInterval || 300;
69
- console.log(`⏰ Setting up periodic ad breaks every ${interval}s`);
70
-
71
- let accumulatedPlaybackTime = 0;
72
- let lastCheckTime = this.video.currentTime;
73
-
74
- this.periodicAdTimer = setInterval(() => {
75
- if (!this.video.paused && !this.isPlayingAd()) {
76
- const currentTime = this.video.currentTime || 0;
77
- const timeDelta = currentTime - lastCheckTime;
78
-
79
- // Only accumulate if delta is reasonable (< 5 seconds)
80
- if (timeDelta > 0 && timeDelta < 5) {
81
- accumulatedPlaybackTime += timeDelta;
82
- }
83
-
84
- lastCheckTime = currentTime;
85
-
86
- const timeSinceLastAd = accumulatedPlaybackTime - this.lastAdBreakTime;
87
-
88
- if (timeSinceLastAd >= interval) {
89
- console.log('⏰ Periodic ad break triggered');
90
- console.log(` Playback time: ${Math.floor(accumulatedPlaybackTime)}s`);
91
- console.log(` Time since last ad: ${Math.floor(timeSinceLastAd)}s`);
92
-
93
- this.liveConfig.onAdBreakScheduled?.(accumulatedPlaybackTime);
94
- this.triggerAdBreak();
95
- this.lastAdBreakTime = accumulatedPlaybackTime;
96
- }
97
- }
98
- }, 1000); // Check every second
99
- }
100
-
101
- /**
102
- * Manually trigger an ad break
103
- */
104
- triggerAdBreak(): void {
105
- if (!this.adsManager) {
106
- console.error('❌ Ads manager not initialized');
107
- return;
108
- }
109
-
110
- if (this.isPlayingAd()) {
111
- console.warn('⚠️ Ad already playing, skipping trigger');
112
- return;
113
- }
114
-
115
- console.log('🎬 Triggering ad break for live stream');
116
-
117
- const currentTime = this.video.currentTime;
118
- console.log(`πŸ“ Stream position before ad: ${currentTime.toFixed(2)}s`);
119
-
120
- // Pause stream download if configured
121
- if (this.liveConfig.pauseStreamDuringAd) {
122
- this.pauseStreamDownload();
123
- }
124
-
125
- // Store position for DVR-style resume
126
- (this.video as any).__positionBeforeAd = currentTime;
127
-
128
- // Request ads
129
- this.adsManager.start();
130
- }
131
-
132
- /**
133
- * Pause stream download during ad
134
- */
135
- private pauseStreamDownload(): void {
136
- try {
137
- if (this.hlsInstance && this.hlsInstance.stopLoad) {
138
- console.log('⏸️ Pausing stream download during ad');
139
- this.hlsInstance.stopLoad();
140
- }
141
- } catch (error) {
142
- console.warn('Could not pause stream download:', error);
143
- }
144
- }
145
-
146
- /**
147
- * Resume stream download after ad
148
- */
149
- private resumeStreamDownload(): void {
150
- try {
151
- if (this.hlsInstance && this.hlsInstance.startLoad) {
152
- console.log('▢️ Resuming stream download after ad');
153
- this.hlsInstance.startLoad();
154
- }
155
- } catch (error) {
156
- console.warn('Could not resume stream download:', error);
157
- }
158
- }
159
-
160
- /**
161
- * Override parent onAdEnd to add live stream logic
162
- */
163
- protected setupLiveEdgeResume(): void {
164
- const originalOnAdEnd = this.liveConfig.onAdEnd;
165
-
166
- this.liveConfig.onAdEnd = () => {
167
- const positionBeforeAd = (this.video as any).__positionBeforeAd;
168
-
169
- if (this.liveConfig.syncToLiveEdge) {
170
- // Live edge sync mode - jump to live
171
- console.log('πŸ“Ί Ad ended, syncing to live edge (skipping content)');
172
- this.resumeStreamDownload();
173
- this.syncToLiveEdge();
174
- } else {
175
- // DVR mode - resume from pause point
176
- console.log('πŸ“Ί Ad ended, resuming from pause point (DVR-style)');
177
- this.resumeFromPausePoint(positionBeforeAd);
178
- }
179
-
180
- // Call original callback
181
- originalOnAdEnd?.();
182
- };
183
- }
184
-
185
- /**
186
- * Resume from paused position (DVR-style)
187
- */
188
- private resumeFromPausePoint(pausedTime: number): void {
189
- setTimeout(() => {
190
- try {
191
- console.log(`⏯️ Resuming from paused position: ${pausedTime.toFixed(2)}s`);
192
-
193
- // Resume stream downloading first
194
- if (this.liveConfig.pauseStreamDuringAd) {
195
- this.resumeStreamDownload();
196
- }
197
-
198
- // Resume from exact pause point
199
- this.video.currentTime = pausedTime;
200
-
201
- // Calculate how far behind live
202
- if (this.hlsInstance && this.hlsInstance.liveSyncPosition !== undefined) {
203
- const liveEdge = this.hlsInstance.liveSyncPosition;
204
- const behindLive = liveEdge - pausedTime;
205
- console.log(` Behind live by: ${behindLive.toFixed(1)} seconds`);
206
- }
207
- } catch (error) {
208
- console.error('❌ Error resuming from pause point:', error);
209
- }
210
- }, 100);
211
- }
212
-
213
- /**
214
- * Sync to live edge
215
- */
216
- private syncToLiveEdge(): void {
217
- setTimeout(() => {
218
- try {
219
- // Resume stream download
220
- this.resumeStreamDownload();
221
-
222
- // Get live edge position
223
- let liveEdgePosition: number | undefined;
224
-
225
- if (this.hlsInstance && this.hlsInstance.liveSyncPosition !== undefined) {
226
- console.log(`πŸ“ HLS.js live edge: ${this.hlsInstance.liveSyncPosition.toFixed(2)}s`);
227
- liveEdgePosition = this.hlsInstance.liveSyncPosition;
228
- }
229
-
230
- if (liveEdgePosition !== undefined) {
231
- const offset = this.liveConfig.liveEdgeOffset || 3;
232
- const targetPosition = liveEdgePosition - offset;
233
- console.log(`⏩ Syncing to live edge: ${targetPosition.toFixed(2)}s (offset: ${offset}s)`);
234
- this.video.currentTime = targetPosition;
235
- } else {
236
- console.warn('⚠️ Could not determine live edge position');
237
- }
238
- } catch (error) {
239
- console.error('❌ Error syncing to live edge:', error);
240
- }
241
- }, 100);
242
- }
243
-
244
- /**
245
- * Cleanup
246
- */
247
- destroy(): void {
248
- if (this.periodicAdTimer) {
249
- clearInterval(this.periodicAdTimer);
250
- this.periodicAdTimer = null;
251
- }
252
-
253
- super.destroy();
254
- }
255
- }