unified-video-framework 1.4.217 → 1.4.218
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/CHANGELOG.md +0 -102
- package/package.json +1 -1
- package/packages/core/dist/interfaces.d.ts +2 -34
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces.ts +3 -51
- package/packages/web/dist/ads/GoogleAdsManager.d.ts +39 -0
- package/packages/web/dist/ads/GoogleAdsManager.d.ts.map +1 -0
- package/packages/web/dist/ads/GoogleAdsManager.js +180 -0
- package/packages/web/dist/ads/GoogleAdsManager.js.map +1 -0
- package/packages/web/dist/index.d.ts +0 -1
- package/packages/web/dist/index.d.ts.map +1 -1
- package/packages/web/dist/index.js +0 -1
- package/packages/web/dist/index.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +90 -0
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +162 -1
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/ads/GoogleAdsManager.ts +358 -0
- package/packages/web/src/index.ts +0 -3
- package/packages/web/src/react/WebPlayerView.tsx +304 -2
- package/packages/web/src/ads/AdsManager.ts +0 -691
- package/packages/web/src/ads/README.md +0 -403
- package/packages/web/src/ads/index.ts +0 -17
- package/packages/web/src/ads/types.ts +0 -442
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google IMA Ads Manager
|
|
3
|
+
* Supports ALL Google ad types:
|
|
4
|
+
* - Pre-roll (before video)
|
|
5
|
+
* - Mid-roll (during video)
|
|
6
|
+
* - Post-roll (after video)
|
|
7
|
+
* - Overlay ads (non-linear)
|
|
8
|
+
* - Companion ads (sidebar/banner)
|
|
9
|
+
* - Bumper ads (short 6s ads)
|
|
10
|
+
* - Skippable & non-skippable ads
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface GoogleAdsConfig {
|
|
14
|
+
// Ad tag URL (VAST/VMAP)
|
|
15
|
+
adTagUrl: string;
|
|
16
|
+
|
|
17
|
+
// Optional: Specific ad break times (for mid-rolls)
|
|
18
|
+
// If not provided, uses VMAP schedule from ad server
|
|
19
|
+
midrollTimes?: number[]; // e.g., [30, 60, 120] = ads at 30s, 60s, 120s
|
|
20
|
+
|
|
21
|
+
// Companion ad containers
|
|
22
|
+
companionAdSlots?: Array<{
|
|
23
|
+
containerId: string; // HTML element ID
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
// Callbacks
|
|
29
|
+
onAdStart?: () => void;
|
|
30
|
+
onAdEnd?: () => void;
|
|
31
|
+
onAdError?: (error: any) => void;
|
|
32
|
+
onAllAdsComplete?: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class GoogleAdsManager {
|
|
36
|
+
private video: HTMLVideoElement;
|
|
37
|
+
private adContainer: HTMLElement;
|
|
38
|
+
private config: GoogleAdsConfig;
|
|
39
|
+
private adsManager: any = null;
|
|
40
|
+
private adsLoader: any = null;
|
|
41
|
+
private adDisplayContainer: any = null;
|
|
42
|
+
private isAdPlaying = false;
|
|
43
|
+
|
|
44
|
+
constructor(video: HTMLVideoElement, adContainer: HTMLElement, config: GoogleAdsConfig) {
|
|
45
|
+
this.video = video;
|
|
46
|
+
this.adContainer = adContainer;
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize ads system
|
|
52
|
+
*/
|
|
53
|
+
async initialize(): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
await this.loadIMASDK();
|
|
56
|
+
this.setupAdsLoader();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to initialize Google Ads:', error);
|
|
59
|
+
this.config.onAdError?.(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load Google IMA SDK
|
|
65
|
+
*/
|
|
66
|
+
private loadIMASDK(): Promise<void> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
if ((window as any).google?.ima) {
|
|
69
|
+
resolve();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const script = document.createElement('script');
|
|
74
|
+
script.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
|
75
|
+
script.async = true;
|
|
76
|
+
script.onload = () => resolve();
|
|
77
|
+
script.onerror = () => reject(new Error('Failed to load Google IMA SDK'));
|
|
78
|
+
document.head.appendChild(script);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Setup ads loader
|
|
84
|
+
*/
|
|
85
|
+
private setupAdsLoader(): void {
|
|
86
|
+
const google = (window as any).google;
|
|
87
|
+
|
|
88
|
+
// Create ad display container
|
|
89
|
+
this.adDisplayContainer = new google.ima.AdDisplayContainer(
|
|
90
|
+
this.adContainer,
|
|
91
|
+
this.video
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Create ads loader
|
|
95
|
+
this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);
|
|
96
|
+
|
|
97
|
+
// Register for ads loaded event
|
|
98
|
+
this.adsLoader.addEventListener(
|
|
99
|
+
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
|
100
|
+
(event: any) => this.onAdsManagerLoaded(event),
|
|
101
|
+
false
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Register for error event
|
|
105
|
+
this.adsLoader.addEventListener(
|
|
106
|
+
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
107
|
+
(event: any) => this.onAdError(event),
|
|
108
|
+
false
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Signal when video content completes (for post-rolls)
|
|
112
|
+
this.video.addEventListener('ended', () => {
|
|
113
|
+
if (!this.isAdPlaying) {
|
|
114
|
+
this.adsLoader?.contentComplete();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Request ads
|
|
121
|
+
*/
|
|
122
|
+
requestAds(): void {
|
|
123
|
+
const google = (window as any).google;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const adsRequest = new google.ima.AdsRequest();
|
|
127
|
+
adsRequest.adTagUrl = this.config.adTagUrl;
|
|
128
|
+
|
|
129
|
+
// Set video dimensions
|
|
130
|
+
adsRequest.linearAdSlotWidth = this.video.clientWidth;
|
|
131
|
+
adsRequest.linearAdSlotHeight = this.video.clientHeight;
|
|
132
|
+
adsRequest.nonLinearAdSlotWidth = this.video.clientWidth;
|
|
133
|
+
adsRequest.nonLinearAdSlotHeight = Math.floor(this.video.clientHeight / 3);
|
|
134
|
+
|
|
135
|
+
// Set companion ad slots if provided
|
|
136
|
+
if (this.config.companionAdSlots && this.config.companionAdSlots.length > 0) {
|
|
137
|
+
const companionAdSlots = this.config.companionAdSlots.map(slot => {
|
|
138
|
+
return new google.ima.CompanionAdSelectionSettings();
|
|
139
|
+
});
|
|
140
|
+
adsRequest.setAdWillAutoPlay(true);
|
|
141
|
+
adsRequest.setAdWillPlayMuted(false);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Request ads
|
|
145
|
+
this.adsLoader.requestAds(adsRequest);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('Error requesting ads:', error);
|
|
148
|
+
this.config.onAdError?.(error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Initialize ad display container (must be called on user gesture)
|
|
154
|
+
*/
|
|
155
|
+
initAdDisplayContainer(): void {
|
|
156
|
+
try {
|
|
157
|
+
this.adDisplayContainer?.initialize();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn('Ad display container already initialized');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handle ads manager loaded
|
|
165
|
+
*/
|
|
166
|
+
private onAdsManagerLoaded(event: any): void {
|
|
167
|
+
const google = (window as any).google;
|
|
168
|
+
|
|
169
|
+
// Setup ads rendering settings
|
|
170
|
+
const adsRenderingSettings = new google.ima.AdsRenderingSettings();
|
|
171
|
+
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
|
|
172
|
+
adsRenderingSettings.enablePreloading = true;
|
|
173
|
+
|
|
174
|
+
// Get the ads manager
|
|
175
|
+
this.adsManager = event.getAdsManager(this.video, adsRenderingSettings);
|
|
176
|
+
|
|
177
|
+
// Setup ads manager event listeners
|
|
178
|
+
this.setupAdsManagerListeners();
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Initialize ads manager
|
|
182
|
+
this.adsManager.init(
|
|
183
|
+
this.video.clientWidth,
|
|
184
|
+
this.video.clientHeight,
|
|
185
|
+
google.ima.ViewMode.NORMAL
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Start ads
|
|
189
|
+
this.adsManager.start();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('Error starting ads:', error);
|
|
192
|
+
this.video.play().catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Setup ads manager event listeners
|
|
198
|
+
*/
|
|
199
|
+
private setupAdsManagerListeners(): void {
|
|
200
|
+
const google = (window as any).google;
|
|
201
|
+
|
|
202
|
+
// Content pause - ad is about to play
|
|
203
|
+
this.adsManager.addEventListener(
|
|
204
|
+
google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
|
|
205
|
+
() => {
|
|
206
|
+
console.log('Ad: Content paused');
|
|
207
|
+
this.isAdPlaying = true;
|
|
208
|
+
this.video.pause();
|
|
209
|
+
this.config.onAdStart?.();
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Content resume - ad finished
|
|
214
|
+
this.adsManager.addEventListener(
|
|
215
|
+
google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
|
|
216
|
+
() => {
|
|
217
|
+
console.log('Ad: Content resume');
|
|
218
|
+
this.isAdPlaying = false;
|
|
219
|
+
this.config.onAdEnd?.();
|
|
220
|
+
this.video.play().catch(() => {});
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Ad started
|
|
225
|
+
this.adsManager.addEventListener(
|
|
226
|
+
google.ima.AdEvent.Type.STARTED,
|
|
227
|
+
(event: any) => {
|
|
228
|
+
const ad = event.getAd();
|
|
229
|
+
console.log('Ad started:', {
|
|
230
|
+
type: ad.isLinear() ? 'Linear (video)' : 'Non-linear (overlay)',
|
|
231
|
+
duration: ad.getDuration(),
|
|
232
|
+
skippable: ad.getSkipTimeOffset() !== -1,
|
|
233
|
+
title: ad.getTitle(),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Ad completed
|
|
239
|
+
this.adsManager.addEventListener(
|
|
240
|
+
google.ima.AdEvent.Type.COMPLETE,
|
|
241
|
+
() => {
|
|
242
|
+
console.log('Ad completed');
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// All ads completed
|
|
247
|
+
this.adsManager.addEventListener(
|
|
248
|
+
google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
|
|
249
|
+
() => {
|
|
250
|
+
console.log('All ads completed');
|
|
251
|
+
this.config.onAllAdsComplete?.();
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Ad error
|
|
256
|
+
this.adsManager.addEventListener(
|
|
257
|
+
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
258
|
+
(event: any) => this.onAdError(event)
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Ad skipped
|
|
262
|
+
this.adsManager.addEventListener(
|
|
263
|
+
google.ima.AdEvent.Type.SKIPPED,
|
|
264
|
+
() => {
|
|
265
|
+
console.log('Ad skipped by user');
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Handle ad error
|
|
272
|
+
*/
|
|
273
|
+
private onAdError(event: any): void {
|
|
274
|
+
const error = event.getError?.();
|
|
275
|
+
console.error('Ad error:', error?.getMessage?.() || error);
|
|
276
|
+
|
|
277
|
+
this.config.onAdError?.(error);
|
|
278
|
+
|
|
279
|
+
// Destroy ads manager on error
|
|
280
|
+
if (this.adsManager) {
|
|
281
|
+
this.adsManager.destroy();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Resume content playback
|
|
285
|
+
this.isAdPlaying = false;
|
|
286
|
+
this.video.play().catch(() => {});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Pause ad
|
|
291
|
+
*/
|
|
292
|
+
pause(): void {
|
|
293
|
+
if (this.adsManager && this.isAdPlaying) {
|
|
294
|
+
this.adsManager.pause();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Resume ad
|
|
300
|
+
*/
|
|
301
|
+
resume(): void {
|
|
302
|
+
if (this.adsManager && this.isAdPlaying) {
|
|
303
|
+
this.adsManager.resume();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Skip ad (if skippable)
|
|
309
|
+
*/
|
|
310
|
+
skip(): void {
|
|
311
|
+
if (this.adsManager) {
|
|
312
|
+
this.adsManager.skip();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resize ads
|
|
318
|
+
*/
|
|
319
|
+
resize(width: number, height: number): void {
|
|
320
|
+
const google = (window as any).google;
|
|
321
|
+
if (this.adsManager) {
|
|
322
|
+
this.adsManager.resize(width, height, google.ima.ViewMode.NORMAL);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Set volume
|
|
328
|
+
*/
|
|
329
|
+
setVolume(volume: number): void {
|
|
330
|
+
if (this.adsManager) {
|
|
331
|
+
this.adsManager.setVolume(volume);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check if ad is currently playing
|
|
337
|
+
*/
|
|
338
|
+
isPlayingAd(): boolean {
|
|
339
|
+
return this.isAdPlaying;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Cleanup
|
|
344
|
+
*/
|
|
345
|
+
destroy(): void {
|
|
346
|
+
if (this.adsManager) {
|
|
347
|
+
this.adsManager.destroy();
|
|
348
|
+
this.adsManager = null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (this.adsLoader) {
|
|
352
|
+
this.adsLoader.destroy();
|
|
353
|
+
this.adsLoader = null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
this.isAdPlaying = false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -14,8 +14,5 @@ export { SecureVideoPlayer } from './SecureVideoPlayer';
|
|
|
14
14
|
// Export EPG (Electronic Program Guide) components
|
|
15
15
|
export * from './react/EPG';
|
|
16
16
|
|
|
17
|
-
// Export Ads module (Google Ads Integration)
|
|
18
|
-
export * from './ads/index';
|
|
19
|
-
|
|
20
17
|
// Version
|
|
21
18
|
export const VERSION = '1.0.0';
|