unified-video-framework 1.4.403 → 1.4.405
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 +3 -2
- package/packages/web/dist/ads/GoogleAdsManager.d.ts.map +1 -1
- package/packages/web/dist/ads/GoogleAdsManager.js +13 -3
- 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 +1 -7
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +3 -34
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/ads/GoogleAdsManager.ts +34 -11
- package/packages/web/src/ads/LiveStreamAdsManager.ts +71 -441
- package/packages/web/src/react/WebPlayerView.tsx +13 -77
- package/packages/web/src/react/examples/live-stream-ads-example.tsx +0 -362
|
@@ -414,27 +414,19 @@ export type WebPlayerViewProps = {
|
|
|
414
414
|
height: number;
|
|
415
415
|
}>;
|
|
416
416
|
|
|
417
|
-
// Live Stream
|
|
418
|
-
liveAdBreakMode?: '
|
|
419
|
-
periodicAdInterval?: number;
|
|
420
|
-
syncToLiveEdge?: boolean;
|
|
421
|
-
liveEdgeOffset?: number;
|
|
422
|
-
pauseStreamDuringAd?: boolean;
|
|
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
|
-
};
|
|
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)
|
|
430
423
|
|
|
431
424
|
// Callbacks
|
|
432
425
|
onAdStart?: () => void; // Called when ad starts
|
|
433
426
|
onAdEnd?: () => void; // Called when ad ends
|
|
434
427
|
onAdError?: (error: any) => void; // Called on ad error
|
|
435
428
|
onAllAdsComplete?: () => void; // Called when all ads complete
|
|
436
|
-
|
|
437
|
-
onAdBreakScheduled?: (scheduledTime: number) => void; // Called when ad scheduled (periodic mode)
|
|
429
|
+
onAdBreakScheduled?: (scheduledTime: number) => void; // Called when ad is scheduled (periodic mode)
|
|
438
430
|
};
|
|
439
431
|
|
|
440
432
|
// Chapter Event Callbacks
|
|
@@ -1244,25 +1236,9 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1244
1236
|
|
|
1245
1237
|
// Initialize Google Ads if configured
|
|
1246
1238
|
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
|
-
|
|
1239
|
+
// Small delay to ensure ad container is properly mounted in DOM
|
|
1261
1240
|
setTimeout(async () => {
|
|
1262
1241
|
try {
|
|
1263
|
-
// Wait for ad container to be in DOM
|
|
1264
|
-
await waitForAdContainer();
|
|
1265
|
-
|
|
1266
1242
|
// Ensure ad container exists
|
|
1267
1243
|
if (!adContainerRef.current) {
|
|
1268
1244
|
console.error('❌ Ad container ref is null - cannot initialize ads');
|
|
@@ -1316,57 +1292,38 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1316
1292
|
adContainer,
|
|
1317
1293
|
{
|
|
1318
1294
|
adTagUrl: props.googleAds.adTagUrl,
|
|
1319
|
-
midrollTimes: props.googleAds.midrollTimes,
|
|
1320
|
-
companionAdSlots: props.googleAds.companionAdSlots,
|
|
1321
|
-
|
|
1322
|
-
// Live stream specific config
|
|
1323
1295
|
liveAdBreakMode: props.googleAds.liveAdBreakMode,
|
|
1324
1296
|
periodicAdInterval: props.googleAds.periodicAdInterval,
|
|
1325
1297
|
syncToLiveEdge: props.googleAds.syncToLiveEdge,
|
|
1326
1298
|
liveEdgeOffset: props.googleAds.liveEdgeOffset,
|
|
1327
1299
|
pauseStreamDuringAd: props.googleAds.pauseStreamDuringAd,
|
|
1328
|
-
metadataConfig: props.googleAds.metadataConfig,
|
|
1329
|
-
|
|
1330
1300
|
onAdStart: () => {
|
|
1331
1301
|
setIsAdPlaying(true);
|
|
1332
1302
|
|
|
1333
|
-
// Check if this is a pre-roll ad
|
|
1303
|
+
// Check if this is a pre-roll ad
|
|
1334
1304
|
if (videoElement.currentTime < 1) {
|
|
1335
|
-
console.log('🎬 Pre-roll ad starting
|
|
1305
|
+
console.log('🎬 Pre-roll ad starting');
|
|
1336
1306
|
isPrerollPlaying = true;
|
|
1337
1307
|
hasPrerollAd = true;
|
|
1338
|
-
|
|
1339
|
-
// Add playback blocker
|
|
1340
1308
|
videoElement.addEventListener('play', blockVideoUntilPreroll);
|
|
1341
1309
|
}
|
|
1342
1310
|
|
|
1343
|
-
// Notify player to block keyboard controls
|
|
1344
1311
|
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1345
1312
|
(player as any).setAdPlaying(true);
|
|
1346
1313
|
}
|
|
1347
1314
|
props.googleAds?.onAdStart?.();
|
|
1348
1315
|
},
|
|
1349
|
-
onLiveAdBreakDetected: props.googleAds.onLiveAdBreakDetected,
|
|
1350
1316
|
onAdBreakScheduled: props.googleAds.onAdBreakScheduled,
|
|
1351
1317
|
onAdEnd: () => {
|
|
1352
1318
|
setIsAdPlaying(false);
|
|
1353
1319
|
|
|
1354
|
-
// Handle pre-roll completion
|
|
1355
1320
|
if (isPrerollPlaying) {
|
|
1356
|
-
console.log('
|
|
1321
|
+
console.log('✅ Pre-roll ad completed - unblocking video');
|
|
1357
1322
|
isPrerollPlaying = false;
|
|
1358
|
-
|
|
1359
|
-
// Remove playback blocker
|
|
1360
1323
|
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
|
-
}
|
|
1324
|
+
videoElement.play().catch(() => {});
|
|
1367
1325
|
}
|
|
1368
1326
|
|
|
1369
|
-
// Notify player to unblock keyboard controls
|
|
1370
1327
|
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1371
1328
|
(player as any).setAdPlaying(false);
|
|
1372
1329
|
}
|
|
@@ -1375,11 +1332,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1375
1332
|
onAdError: (error) => {
|
|
1376
1333
|
setIsAdPlaying(false);
|
|
1377
1334
|
isPrerollPlaying = false;
|
|
1378
|
-
|
|
1379
|
-
// Remove blocker on error
|
|
1380
1335
|
videoElement.removeEventListener('play', blockVideoUntilPreroll);
|
|
1381
|
-
|
|
1382
|
-
// Notify player to unblock keyboard controls
|
|
1383
1336
|
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1384
1337
|
(player as any).setAdPlaying(false);
|
|
1385
1338
|
}
|
|
@@ -1388,29 +1341,12 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1388
1341
|
onAllAdsComplete: () => {
|
|
1389
1342
|
setIsAdPlaying(false);
|
|
1390
1343
|
isPrerollPlaying = false;
|
|
1391
|
-
|
|
1392
|
-
// Remove blocker when all ads complete
|
|
1393
1344
|
videoElement.removeEventListener('play', blockVideoUntilPreroll);
|
|
1394
|
-
|
|
1395
|
-
// Notify player to unblock keyboard controls
|
|
1396
1345
|
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1397
1346
|
(player as any).setAdPlaying(false);
|
|
1398
1347
|
}
|
|
1399
1348
|
props.googleAds?.onAllAdsComplete?.();
|
|
1400
1349
|
},
|
|
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
1350
|
}
|
|
1415
1351
|
)
|
|
1416
1352
|
: new GoogleAdsManager(
|
|
@@ -1507,7 +1443,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1507
1443
|
|
|
1508
1444
|
// Initialize ads manager (different for live vs VOD)
|
|
1509
1445
|
if (isLiveAdMode) {
|
|
1510
|
-
// For LiveStreamAdsManager, pass HLS instance for
|
|
1446
|
+
// For LiveStreamAdsManager, pass HLS instance for live edge detection
|
|
1511
1447
|
const hlsInstance = (player as any).hls || (player as any).getHlsInstance?.();
|
|
1512
1448
|
await (adsManager as LiveStreamAdsManager).initializeLiveAds(hlsInstance);
|
|
1513
1449
|
console.log('✅ Live Stream Ads initialized successfully');
|
|
@@ -1,362 +0,0 @@
|
|
|
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;
|