unified-video-framework 1.4.399 → 1.4.401

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,6 +4,7 @@ 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';
7
8
  // EPG imports - conditionally loaded
8
9
  import type { EPGData, EPGConfig, EPGProgram, EPGProgramRow } from './types/EPGTypes';
9
10
  import type { VCManifest, VCProduct, VCEvent } from './types/VideoCommerceTypes';
@@ -406,16 +407,34 @@ export type WebPlayerViewProps = {
406
407
  // Google Ads Configuration
407
408
  googleAds?: {
408
409
  adTagUrl: string; // VAST/VMAP ad tag URL
409
- midrollTimes?: number[]; // Mid-roll ad times in seconds [30, 60, 120]
410
+ midrollTimes?: number[]; // Mid-roll ad times in seconds [30, 60, 120] (VOD only)
410
411
  companionAdSlots?: Array<{ // Companion ad containers
411
412
  containerId: string;
412
413
  width: number;
413
414
  height: number;
414
415
  }>;
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
415
432
  onAdStart?: () => void; // Called when ad starts
416
433
  onAdEnd?: () => void; // Called when ad ends
417
434
  onAdError?: (error: any) => void; // Called on ad error
418
435
  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)
419
438
  };
420
439
 
421
440
  // Chapter Event Callbacks
@@ -1225,9 +1244,25 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1225
1244
 
1226
1245
  // Initialize Google Ads if configured
1227
1246
  if (props.googleAds) {
1228
- // Small delay to ensure ad container is properly mounted in DOM
1247
+ // Wait for ad container to be properly mounted in DOM
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
+
1229
1261
  setTimeout(async () => {
1230
1262
  try {
1263
+ // Wait for ad container to be in DOM
1264
+ await waitForAdContainer();
1265
+
1231
1266
  // Ensure ad container exists
1232
1267
  if (!adContainerRef.current) {
1233
1268
  console.error('❌ Ad container ref is null - cannot initialize ads');
@@ -1270,14 +1305,122 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1270
1305
  return true;
1271
1306
  };
1272
1307
 
1273
- const adsManager = new GoogleAdsManager(
1274
- videoElement,
1275
- adContainer,
1276
- {
1277
- adTagUrl: props.googleAds.adTagUrl,
1278
- midrollTimes: props.googleAds.midrollTimes,
1279
- companionAdSlots: props.googleAds.companionAdSlots,
1280
- onAdStart: () => {
1308
+ // Determine if this is a live stream with live ad modes
1309
+ const isLiveAdMode = props.googleAds.liveAdBreakMode &&
1310
+ props.googleAds.liveAdBreakMode !== 'manual';
1311
+
1312
+ // Use LiveStreamAdsManager for live ad modes, otherwise use GoogleAdsManager
1313
+ const adsManager = isLiveAdMode
1314
+ ? new LiveStreamAdsManager(
1315
+ videoElement,
1316
+ adContainer,
1317
+ {
1318
+ adTagUrl: props.googleAds.adTagUrl,
1319
+ midrollTimes: props.googleAds.midrollTimes,
1320
+ companionAdSlots: props.googleAds.companionAdSlots,
1321
+
1322
+ // Live stream specific config
1323
+ liveAdBreakMode: props.googleAds.liveAdBreakMode,
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
1424
  setIsAdPlaying(true);
1282
1425
 
1283
1426
  // Check if this is a pre-roll ad (video time near 0)
@@ -1361,11 +1504,20 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1361
1504
  },
1362
1505
  }
1363
1506
  );
1364
-
1365
- await adsManager.initialize();
1366
- adsManagerRef.current = adsManager;
1367
1507
 
1368
- console.log('✅ Google Ads initialized successfully');
1508
+ // Initialize ads manager (different for live vs VOD)
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
+
1520
+ adsManagerRef.current = adsManager;
1369
1521
 
1370
1522
  // Move ad container into player wrapper for fullscreen support
1371
1523
  const playerWrapper = containerRef.current?.querySelector('.uvf-player-wrapper');
@@ -0,0 +1,362 @@
1
+ import React, { useState } from 'react';
2
+ import { WebPlayerView } from '../WebPlayerView';
3
+
4
+ /**
5
+ * Example: Live Stream Ads with Metadata and Periodic Modes
6
+ *
7
+ * This example demonstrates how to integrate ads with live streaming using:
8
+ * 1. Metadata-based ad insertion (detects cues in HLS stream)
9
+ * 2. Periodic timer-based ad insertion (every X seconds)
10
+ * 3. Hybrid mode (both metadata and periodic)
11
+ */
12
+
13
+ export const LiveStreamAdsExample: React.FC = () => {
14
+ const [adEvents, setAdEvents] = useState<string[]>([]);
15
+ const [mode, setMode] = useState<'metadata' | 'periodic' | 'hybrid'>('periodic');
16
+
17
+ const logAdEvent = (event: string) => {
18
+ console.log(`[Ad Event] ${event}`);
19
+ setAdEvents(prev => [...prev, `${new Date().toLocaleTimeString()}: ${event}`].slice(-10)); // Keep last 10
20
+ };
21
+
22
+ return (
23
+ <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
24
+ {/* Mode Selector */}
25
+ <div style={{ padding: '20px', backgroundColor: '#f5f5f5', borderBottom: '1px solid #ddd' }}>
26
+ <h2 style={{ margin: '0 0 15px 0' }}>Live Stream Ads - Example</h2>
27
+
28
+ <div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
29
+ <button
30
+ onClick={() => setMode('metadata')}
31
+ style={{
32
+ padding: '10px 20px',
33
+ backgroundColor: mode === 'metadata' ? '#007bff' : '#6c757d',
34
+ color: 'white',
35
+ border: 'none',
36
+ borderRadius: '4px',
37
+ cursor: 'pointer'
38
+ }}
39
+ >
40
+ Metadata Mode
41
+ </button>
42
+
43
+ <button
44
+ onClick={() => setMode('periodic')}
45
+ style={{
46
+ padding: '10px 20px',
47
+ backgroundColor: mode === 'periodic' ? '#007bff' : '#6c757d',
48
+ color: 'white',
49
+ border: 'none',
50
+ borderRadius: '4px',
51
+ cursor: 'pointer'
52
+ }}
53
+ >
54
+ Periodic Mode (Every 2 min)
55
+ </button>
56
+
57
+ <button
58
+ onClick={() => setMode('hybrid')}
59
+ style={{
60
+ padding: '10px 20px',
61
+ backgroundColor: mode === 'hybrid' ? '#007bff' : '#6c757d',
62
+ color: 'white',
63
+ border: 'none',
64
+ borderRadius: '4px',
65
+ cursor: 'pointer'
66
+ }}
67
+ >
68
+ Hybrid Mode
69
+ </button>
70
+ </div>
71
+
72
+ <div style={{ fontSize: '14px', color: '#666' }}>
73
+ <strong>Current Mode:</strong> {mode}
74
+ {mode === 'metadata' && ' - Detects #EXT-X-DATERANGE tags in HLS stream'}
75
+ {mode === 'periodic' && ' - Shows ads every 2 minutes of playback'}
76
+ {mode === 'hybrid' && ' - Uses both metadata detection AND periodic fallback'}
77
+ </div>
78
+ </div>
79
+
80
+ {/* Video Player */}
81
+ <div style={{ flex: 1, position: 'relative', backgroundColor: '#000' }}>
82
+ <WebPlayerView
83
+ // Live HLS stream
84
+ url="https://stream.example.com/live.m3u8"
85
+ type="hls"
86
+
87
+ // Player config
88
+ autoPlay={false}
89
+ controls={true}
90
+ muted={true} // Start muted for autoplay compatibility
91
+
92
+ // Google Ads for Live Streaming
93
+ googleAds={{
94
+ // VAST ad tag URL
95
+ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dlinear&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=',
96
+
97
+ // ========================================
98
+ // LIVE STREAM AD CONFIGURATION
99
+ // ========================================
100
+
101
+ // Choose ad insertion mode
102
+ liveAdBreakMode: mode,
103
+
104
+ // For periodic mode: ad every X seconds (default: 300 = 5 minutes)
105
+ periodicAdInterval: 120, // 2 minutes for demo
106
+
107
+ // Sync to live edge after ad (recommended for live streams)
108
+ syncToLiveEdge: true,
109
+ liveEdgeOffset: 3, // Stay 3 seconds behind live edge
110
+
111
+ // Metadata detection config (for metadata/hybrid modes)
112
+ metadataConfig: {
113
+ detectDateRange: true, // Detect #EXT-X-DATERANGE tags
114
+ detectID3: true, // Detect ID3 timed metadata
115
+ adClassNames: ['com.google.ads', 'ads', 'ad-break']
116
+ },
117
+
118
+ // ========================================
119
+ // AD EVENT CALLBACKS
120
+ // ========================================
121
+
122
+ onAdStart: () => {
123
+ logAdEvent(`🎬 Ad Started (Mode: ${mode})`);
124
+ },
125
+
126
+ onAdEnd: () => {
127
+ logAdEvent('✅ Ad Ended - Resuming Live Stream');
128
+ },
129
+
130
+ onAdError: (error: any) => {
131
+ logAdEvent(`❌ Ad Error: ${error?.getMessage?.() || error}`);
132
+ },
133
+
134
+ onAllAdsComplete: () => {
135
+ logAdEvent('✅ All Ads in Pod Completed');
136
+ },
137
+
138
+ // Live-specific callbacks
139
+ onLiveAdBreakDetected: (metadata) => {
140
+ logAdEvent(`📍 Live Ad Cue Detected: ${JSON.stringify(metadata).slice(0, 50)}...`);
141
+ },
142
+
143
+ onAdBreakScheduled: (scheduledTime) => {
144
+ logAdEvent(`⏰ Ad Break Scheduled at ${scheduledTime.toFixed(0)}s playback time`);
145
+ },
146
+ }}
147
+
148
+ // Player callbacks
149
+ onReady={(player) => {
150
+ console.log('Player ready with live stream ads');
151
+ logAdEvent('🎥 Player Ready');
152
+ }}
153
+
154
+ onError={(error) => {
155
+ console.error('Player error:', error);
156
+ logAdEvent(`❌ Player Error: ${error instanceof Error ? error.message : String(error)}`);
157
+ }}
158
+
159
+ onTimeUpdate={(time) => {
160
+ // Optional: Track playback time
161
+ }}
162
+ />
163
+ </div>
164
+
165
+ {/* Ad Event Log */}
166
+ <div style={{
167
+ padding: '20px',
168
+ backgroundColor: '#f9f9f9',
169
+ borderTop: '1px solid #ddd',
170
+ maxHeight: '200px',
171
+ overflowY: 'auto',
172
+ }}>
173
+ <h3 style={{ margin: '0 0 10px 0', fontSize: '16px' }}>Live Ad Events Log:</h3>
174
+ {adEvents.length === 0 ? (
175
+ <p style={{ color: '#999', margin: 0 }}>No ad events yet. Play the video to see ads.</p>
176
+ ) : (
177
+ <ul style={{ margin: 0, padding: '0 0 0 20px' }}>
178
+ {adEvents.map((event, index) => (
179
+ <li key={index} style={{ fontSize: '14px', marginBottom: '5px' }}>
180
+ {event}
181
+ </li>
182
+ ))}
183
+ </ul>
184
+ )}
185
+ </div>
186
+
187
+ {/* Implementation Notes */}
188
+ <div style={{
189
+ padding: '20px',
190
+ backgroundColor: '#e7f3ff',
191
+ borderTop: '1px solid #b3d9ff'
192
+ }}>
193
+ <h3 style={{ margin: '0 0 10px 0', fontSize: '16px' }}>💡 Implementation Notes:</h3>
194
+ <ul style={{ margin: 0, paddingLeft: '20px', fontSize: '14px' }}>
195
+ <li>
196
+ <strong>Metadata Mode:</strong> Requires HLS stream with #EXT-X-DATERANGE tags or ID3 metadata.
197
+ Your encoder must embed ad cues in the manifest.
198
+ </li>
199
+ <li>
200
+ <strong>Periodic Mode:</strong> Works with any live stream. Ads trigger every X seconds of actual playback.
201
+ Simple but users see ads at different times (not synchronized).
202
+ </li>
203
+ <li>
204
+ <strong>Hybrid Mode:</strong> Uses metadata when available, falls back to periodic timer.
205
+ Best of both worlds!
206
+ </li>
207
+ <li>
208
+ <strong>Live Edge Sync:</strong> After ads, player jumps to live edge (catches up to "now").
209
+ Disable if you want DVR-style behavior (continue from where paused).
210
+ </li>
211
+ </ul>
212
+ </div>
213
+ </div>
214
+ );
215
+ };
216
+
217
+ /**
218
+ * ========================================================================
219
+ * USAGE EXAMPLE 1: METADATA-BASED LIVE ADS
220
+ * ========================================================================
221
+ */
222
+ export const MetadataLiveAdsExample: React.FC = () => {
223
+ return (
224
+ <WebPlayerView
225
+ url="https://stream.example.com/live.m3u8"
226
+ type="hls"
227
+
228
+ googleAds={{
229
+ adTagUrl: 'https://pubads.g.doubleclick.net/...',
230
+
231
+ // Use metadata mode
232
+ liveAdBreakMode: 'metadata',
233
+
234
+ // Metadata detection config
235
+ metadataConfig: {
236
+ detectDateRange: true,
237
+ detectID3: true,
238
+ adClassNames: ['com.google.ads', 'ads']
239
+ },
240
+
241
+ // Sync to live edge after ads
242
+ syncToLiveEdge: true,
243
+ liveEdgeOffset: 3,
244
+
245
+ onLiveAdBreakDetected: (metadata) => {
246
+ console.log('Ad cue detected:', metadata);
247
+ }
248
+ }}
249
+ />
250
+ );
251
+ };
252
+
253
+ /**
254
+ * ========================================================================
255
+ * USAGE EXAMPLE 2: PERIODIC TIMER-BASED LIVE ADS
256
+ * ========================================================================
257
+ */
258
+ export const PeriodicLiveAdsExample: React.FC = () => {
259
+ return (
260
+ <WebPlayerView
261
+ url="https://stream.example.com/live.m3u8"
262
+ type="hls"
263
+
264
+ googleAds={{
265
+ adTagUrl: 'https://pubads.g.doubleclick.net/...',
266
+
267
+ // Use periodic mode
268
+ liveAdBreakMode: 'periodic',
269
+
270
+ // Ad every 5 minutes (300 seconds)
271
+ periodicAdInterval: 300,
272
+
273
+ // Sync to live edge after ads
274
+ syncToLiveEdge: true,
275
+
276
+ onAdBreakScheduled: (scheduledTime) => {
277
+ console.log(`Next ad at ${scheduledTime}s playback time`);
278
+ }
279
+ }}
280
+ />
281
+ );
282
+ };
283
+
284
+ /**
285
+ * ========================================================================
286
+ * USAGE EXAMPLE 3: HYBRID MODE (METADATA + PERIODIC FALLBACK)
287
+ * ========================================================================
288
+ */
289
+ export const HybridLiveAdsExample: React.FC = () => {
290
+ return (
291
+ <WebPlayerView
292
+ url="https://stream.example.com/live.m3u8"
293
+ type="hls"
294
+
295
+ googleAds={{
296
+ adTagUrl: 'https://pubads.g.doubleclick.net/...',
297
+
298
+ // Use hybrid mode
299
+ liveAdBreakMode: 'hybrid',
300
+
301
+ // Metadata detection (primary)
302
+ metadataConfig: {
303
+ detectDateRange: true,
304
+ detectID3: true
305
+ },
306
+
307
+ // Periodic fallback (if no metadata detected)
308
+ periodicAdInterval: 300,
309
+
310
+ // Sync to live edge
311
+ syncToLiveEdge: true,
312
+
313
+ // Callbacks for both modes
314
+ onLiveAdBreakDetected: (metadata) => {
315
+ console.log('✅ Metadata ad cue detected');
316
+ },
317
+
318
+ onAdBreakScheduled: (scheduledTime) => {
319
+ console.log('⏰ Fallback periodic ad scheduled');
320
+ }
321
+ }}
322
+ />
323
+ );
324
+ };
325
+
326
+ /**
327
+ * ========================================================================
328
+ * BACKEND-CONTROLLED EXAMPLE
329
+ * ========================================================================
330
+ * Fetch ad schedule from backend and configure player accordingly
331
+ */
332
+ export const BackendControlledLiveAdsExample: React.FC = () => {
333
+ const [adConfig, setAdConfig] = useState<any>(null);
334
+
335
+ // Fetch ad configuration from backend
336
+ React.useEffect(() => {
337
+ fetch('/api/live-stream/ad-config')
338
+ .then(res => res.json())
339
+ .then(config => {
340
+ setAdConfig(config);
341
+ });
342
+ }, []);
343
+
344
+ if (!adConfig) return <div>Loading...</div>;
345
+
346
+ return (
347
+ <WebPlayerView
348
+ url={adConfig.streamUrl}
349
+ type="hls"
350
+
351
+ googleAds={{
352
+ adTagUrl: adConfig.adTagUrl,
353
+ liveAdBreakMode: adConfig.mode, // Backend decides: metadata | periodic | hybrid
354
+ periodicAdInterval: adConfig.periodicInterval,
355
+ metadataConfig: adConfig.metadataConfig,
356
+ syncToLiveEdge: true
357
+ }}
358
+ />
359
+ );
360
+ };
361
+
362
+ export default LiveStreamAdsExample;