unified-video-framework 1.4.217 → 1.4.219

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +0 -102
  2. package/package.json +1 -1
  3. package/packages/core/dist/interfaces.d.ts +2 -34
  4. package/packages/core/dist/interfaces.d.ts.map +1 -1
  5. package/packages/core/src/interfaces.ts +3 -51
  6. package/packages/web/dist/ads/GoogleAdsManager.d.ts +39 -0
  7. package/packages/web/dist/ads/GoogleAdsManager.d.ts.map +1 -0
  8. package/packages/web/dist/ads/GoogleAdsManager.js +180 -0
  9. package/packages/web/dist/ads/GoogleAdsManager.js.map +1 -0
  10. package/packages/web/dist/index.d.ts +0 -1
  11. package/packages/web/dist/index.d.ts.map +1 -1
  12. package/packages/web/dist/index.js +0 -1
  13. package/packages/web/dist/index.js.map +1 -1
  14. package/packages/web/dist/react/WebPlayerView.d.ts +103 -0
  15. package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
  16. package/packages/web/dist/react/WebPlayerView.js +207 -2
  17. package/packages/web/dist/react/WebPlayerView.js.map +1 -1
  18. package/packages/web/dist/react/examples/google-ads-example.d.ts +4 -0
  19. package/packages/web/dist/react/examples/google-ads-example.d.ts.map +1 -0
  20. package/packages/web/dist/react/examples/google-ads-example.js +84 -0
  21. package/packages/web/dist/react/examples/google-ads-example.js.map +1 -0
  22. package/packages/web/src/ads/GoogleAdsManager.ts +358 -0
  23. package/packages/web/src/index.ts +0 -3
  24. package/packages/web/src/react/WebPlayerView.tsx +386 -3
  25. package/packages/web/src/react/examples/google-ads-example.tsx +196 -0
  26. package/packages/web/src/ads/AdsManager.ts +0 -691
  27. package/packages/web/src/ads/README.md +0 -403
  28. package/packages/web/src/ads/index.ts +0 -17
  29. package/packages/web/src/ads/types.ts +0 -442
@@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState, useCallback } from 'react';
3
3
  import type { CSSProperties } from 'react';
4
4
  import type { VideoSource, SubtitleTrack, VideoMetadata, PlayerConfig } from '../../core/dist';
5
5
  import { WebPlayer } from '../WebPlayer';
6
+ import { GoogleAdsManager } from '../ads/GoogleAdsManager';
6
7
  // EPG imports - conditionally loaded
7
8
  import type { EPGData, EPGConfig, EPGProgram, EPGProgramRow } from './types/EPGTypes';
8
9
  let EPGOverlay: React.ComponentType<any> | null = null;
@@ -20,6 +21,76 @@ const loadEPGComponents = async () => {
20
21
  return EPGOverlay;
21
22
  };
22
23
 
24
+ // Chapter API interface exposed to parent component
25
+ export interface ChapterAPI {
26
+ loadChapters: (chapters: any) => Promise<void>;
27
+ loadChaptersFromUrl: (url: string) => Promise<void>;
28
+ getCurrentSegment: () => any | null;
29
+ skipToSegment: (segmentId: string) => void;
30
+ getSegments: () => any[];
31
+ updateChapterConfig: (config: any) => void;
32
+ hasChapters: () => boolean;
33
+ getChapters: () => any | null;
34
+ getCoreChapters: () => any[];
35
+ getCoreSegments: () => any[];
36
+ getCurrentChapterInfo: () => any | null;
37
+ seekToChapter: (chapterId: string) => void;
38
+ getNextChapter: () => any | null;
39
+ getPreviousChapter: () => any | null;
40
+ }
41
+
42
+ // Quality control API
43
+ export interface QualityAPI {
44
+ getQualities: () => any[];
45
+ getCurrentQuality: () => any | null;
46
+ setQuality: (index: number) => void;
47
+ setAutoQuality: (enabled: boolean) => void;
48
+ }
49
+
50
+ // EPG API interface
51
+ export interface EPGControlAPI {
52
+ setEPGData: (data: any) => void;
53
+ showEPGButton: () => void;
54
+ hideEPGButton: () => void;
55
+ isEPGButtonVisible: () => boolean;
56
+ }
57
+
58
+ // UI Helper API
59
+ export interface UIHelperAPI {
60
+ focusPlayer: () => void;
61
+ showFullscreenTip: () => void;
62
+ triggerFullscreenButton: () => void;
63
+ showTemporaryMessage: (message: string) => void;
64
+ showFullscreenInstructions: () => void;
65
+ enterFullscreenSynchronously: () => void;
66
+ }
67
+
68
+ // Fullscreen API
69
+ export interface FullscreenAPI {
70
+ enterFullscreen: () => Promise<void>;
71
+ exitFullscreen: () => Promise<void>;
72
+ toggleFullscreen: () => Promise<void>;
73
+ enterPictureInPicture: () => Promise<void>;
74
+ exitPictureInPicture: () => Promise<void>;
75
+ }
76
+
77
+ // Playback control API
78
+ export interface PlaybackAPI {
79
+ play: () => Promise<void>;
80
+ pause: () => void;
81
+ requestPause: () => void;
82
+ seek: (time: number) => void;
83
+ setVolume: (level: number) => void;
84
+ mute: () => void;
85
+ unmute: () => void;
86
+ toggleMute: () => void;
87
+ setPlaybackRate: (rate: number) => void;
88
+ getPlaybackRate: () => number;
89
+ getCurrentTime: () => number;
90
+ getDuration: () => number;
91
+ getState: () => any;
92
+ }
93
+
23
94
  export type WebPlayerViewProps = {
24
95
  // Player config
25
96
  autoPlay?: boolean;
@@ -144,9 +215,45 @@ export type WebPlayerViewProps = {
144
215
  };
145
216
  };
146
217
 
218
+ // Settings customization
219
+ settingsScrollbar?: {
220
+ style?: 'default' | 'compact' | 'overlay';
221
+ widthPx?: number;
222
+ intensity?: number;
223
+ };
224
+
225
+ // Auto-focus player on mount
226
+ autoFocusPlayer?: boolean;
227
+
228
+ // Show fullscreen tip on mount
229
+ showFullscreenTipOnMount?: boolean;
230
+
231
+ // Player instance ref (for imperative control)
232
+ playerRef?: React.RefObject<WebPlayer>;
233
+
234
+ // API callbacks - expose imperative APIs to parent
235
+ onChapterAPI?: (api: ChapterAPI) => void;
236
+ onQualityAPI?: (api: QualityAPI) => void;
237
+ onEPGAPI?: (api: EPGControlAPI) => void;
238
+ onUIHelperAPI?: (api: UIHelperAPI) => void;
239
+ onFullscreenAPI?: (api: FullscreenAPI) => void;
240
+ onPlaybackAPI?: (api: PlaybackAPI) => void;
241
+
147
242
  // Callbacks
148
243
  onReady?: (player: WebPlayer) => void;
149
244
  onError?: (error: unknown) => void;
245
+
246
+ // Additional player event callbacks
247
+ onPlay?: () => void;
248
+ onPause?: () => void;
249
+ onEnded?: () => void;
250
+ onTimeUpdate?: (data: { currentTime: number; duration: number }) => void;
251
+ onProgress?: (data: { buffered: number }) => void;
252
+ onVolumeChange?: (data: { volume: number; muted: boolean }) => void;
253
+ onQualityChange?: (quality: any) => void;
254
+ onBuffering?: (isBuffering: boolean) => void;
255
+ onFullscreenChange?: (isFullscreen: boolean) => void;
256
+ onPictureInPictureChange?: (isPiP: boolean) => void;
150
257
 
151
258
  // EPG (Electronic Program Guide) support
152
259
  epg?: EPGData;
@@ -243,6 +350,21 @@ export type WebPlayerViewProps = {
243
350
  onNavigationBackClicked?: () => void; // Back button clicked
244
351
  onNavigationCloseClicked?: () => void; // Close button clicked
245
352
 
353
+ // Google Ads Configuration
354
+ googleAds?: {
355
+ adTagUrl: string; // VAST/VMAP ad tag URL
356
+ midrollTimes?: number[]; // Mid-roll ad times in seconds [30, 60, 120]
357
+ companionAdSlots?: Array<{ // Companion ad containers
358
+ containerId: string;
359
+ width: number;
360
+ height: number;
361
+ }>;
362
+ onAdStart?: () => void; // Called when ad starts
363
+ onAdEnd?: () => void; // Called when ad ends
364
+ onAdError?: (error: any) => void; // Called on ad error
365
+ onAllAdsComplete?: () => void; // Called when all ads complete
366
+ };
367
+
246
368
  // Chapter Event Callbacks
247
369
  onChapterChange?: (chapter: any) => void; // Core chapter changed
248
370
  onSegmentEntered?: (segment: any) => void; // Segment entered
@@ -258,7 +380,9 @@ export type WebPlayerViewProps = {
258
380
 
259
381
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
260
382
  const containerRef = useRef<HTMLDivElement>(null);
261
- const playerRef = useRef<WebPlayer | null>(null);
383
+ const internalPlayerRef = useRef<WebPlayer | null>(null);
384
+ // Use external ref if provided, otherwise use internal
385
+ const playerRef = props.playerRef || internalPlayerRef;
262
386
  const [dimensions, setDimensions] = useState({
263
387
  width: typeof window !== 'undefined' ? window.innerWidth : 1920,
264
388
  height: typeof window !== 'undefined' ? window.innerHeight : 1080,
@@ -268,6 +392,10 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
268
392
  const [epgVisible, setEPGVisible] = useState(props.showEPG || false);
269
393
  const [playerReady, setPlayerReady] = useState(false);
270
394
  const [epgComponentLoaded, setEPGComponentLoaded] = useState(false);
395
+
396
+ // Google Ads state
397
+ const adsManagerRef = useRef<GoogleAdsManager | null>(null);
398
+ const adContainerRef = useRef<HTMLDivElement>(null);
271
399
 
272
400
  // Responsive window resize handler
273
401
  useEffect(() => {
@@ -479,6 +607,9 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
479
607
 
480
608
  const player = new WebPlayer();
481
609
  playerRef.current = player;
610
+
611
+ // Expose all APIs to parent component via callbacks
612
+ exposeAPIsToParent(player);
482
613
 
483
614
  // Optionally load Google Cast sender SDK
484
615
  if (props.cast) {
@@ -626,6 +757,21 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
626
757
  if (!cancelled) {
627
758
  setPlayerReady(true);
628
759
 
760
+ // Apply settings scrollbar configuration if provided
761
+ if (props.settingsScrollbar) {
762
+ applySettingsScrollbar(player, props.settingsScrollbar);
763
+ }
764
+
765
+ // Auto-focus player if requested
766
+ if (props.autoFocusPlayer && typeof (player as any).focusPlayer === 'function') {
767
+ (player as any).focusPlayer();
768
+ }
769
+
770
+ // Show fullscreen tip if requested
771
+ if (props.showFullscreenTipOnMount && typeof (player as any).showFullscreenTip === 'function') {
772
+ (player as any).showFullscreenTip();
773
+ }
774
+
629
775
  // Set up EPG integration
630
776
  if (props.epg && typeof (player as any).setEPGData === 'function') {
631
777
  (player as any).setEPGData(props.epg);
@@ -678,6 +824,84 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
678
824
  (player as any).on('navigationCloseClicked', props.onNavigationCloseClicked);
679
825
  }
680
826
 
827
+ // Additional event listeners
828
+ if (props.onPlay && typeof (player as any).on === 'function') {
829
+ (player as any).on('onPlay', props.onPlay);
830
+ }
831
+ if (props.onPause && typeof (player as any).on === 'function') {
832
+ (player as any).on('onPause', props.onPause);
833
+ }
834
+ if (props.onEnded && typeof (player as any).on === 'function') {
835
+ (player as any).on('onEnded', props.onEnded);
836
+ }
837
+ if (props.onTimeUpdate && typeof (player as any).on === 'function') {
838
+ (player as any).on('onTimeUpdate', (currentTime: number) => {
839
+ props.onTimeUpdate?.({
840
+ currentTime,
841
+ duration: (player as any).getDuration ? (player as any).getDuration() : 0
842
+ });
843
+ });
844
+ }
845
+ if (props.onProgress && typeof (player as any).on === 'function') {
846
+ (player as any).on('onProgress', (buffered: number) => {
847
+ props.onProgress?.({ buffered });
848
+ });
849
+ }
850
+ if (props.onVolumeChange && typeof (player as any).on === 'function') {
851
+ (player as any).on('onVolumeChanged', (volume: number) => {
852
+ const state = (player as any).getState ? (player as any).getState() : {};
853
+ props.onVolumeChange?.({ volume, muted: state.isMuted || false });
854
+ });
855
+ }
856
+ if (props.onQualityChange && typeof (player as any).on === 'function') {
857
+ (player as any).on('onQualityChanged', props.onQualityChange);
858
+ }
859
+ if (props.onBuffering && typeof (player as any).on === 'function') {
860
+ (player as any).on('onBuffering', props.onBuffering);
861
+ }
862
+ if (props.onFullscreenChange && typeof (player as any).on === 'function') {
863
+ (player as any).on('onFullscreenChanged', props.onFullscreenChange);
864
+ }
865
+ if (props.onPictureInPictureChange && typeof (player as any).on === 'function') {
866
+ (player as any).on('onPictureInPicturechange', props.onPictureInPictureChange);
867
+ }
868
+
869
+ // Initialize Google Ads if configured
870
+ if (props.googleAds && adContainerRef.current) {
871
+ try {
872
+ const videoElement = (player as any).video || (player as any).getVideoElement?.();
873
+ if (videoElement) {
874
+ const adsManager = new GoogleAdsManager(
875
+ videoElement,
876
+ adContainerRef.current,
877
+ {
878
+ adTagUrl: props.googleAds.adTagUrl,
879
+ midrollTimes: props.googleAds.midrollTimes,
880
+ companionAdSlots: props.googleAds.companionAdSlots,
881
+ onAdStart: props.googleAds.onAdStart,
882
+ onAdEnd: props.googleAds.onAdEnd,
883
+ onAdError: props.googleAds.onAdError,
884
+ onAllAdsComplete: props.googleAds.onAllAdsComplete,
885
+ }
886
+ );
887
+
888
+ await adsManager.initialize();
889
+ adsManagerRef.current = adsManager;
890
+
891
+ // Initialize ad display container on first play
892
+ const handleFirstPlay = () => {
893
+ adsManager.initAdDisplayContainer();
894
+ adsManager.requestAds();
895
+ videoElement.removeEventListener('play', handleFirstPlay);
896
+ };
897
+ videoElement.addEventListener('play', handleFirstPlay, { once: true });
898
+ }
899
+ } catch (adsError) {
900
+ console.error('Failed to initialize Google Ads:', adsError);
901
+ props.googleAds.onAdError?.(adsError);
902
+ }
903
+ }
904
+
681
905
  props.onReady?.(player);
682
906
  }
683
907
  } catch (err) {
@@ -689,6 +913,13 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
689
913
 
690
914
  return () => {
691
915
  cancelled = true;
916
+
917
+ // Cleanup ads manager
918
+ if (adsManagerRef.current) {
919
+ adsManagerRef.current.destroy();
920
+ adsManagerRef.current = null;
921
+ }
922
+
692
923
  if (playerRef.current) {
693
924
  playerRef.current.destroy().catch(() => {});
694
925
  playerRef.current = null;
@@ -721,7 +952,142 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
721
952
  props.showFrameworkBranding,
722
953
  JSON.stringify(props.watermark),
723
954
  JSON.stringify(props.navigation),
724
- ])
955
+ JSON.stringify(props.googleAds),
956
+ ]);
957
+
958
+ // Helper function to expose all APIs to parent
959
+ const exposeAPIsToParent = useCallback((player: WebPlayer) => {
960
+ const p = player as any;
961
+
962
+ // Chapter API
963
+ if (props.onChapterAPI) {
964
+ const chapterAPI: ChapterAPI = {
965
+ loadChapters: (chapters: any) => p.loadChapters ? p.loadChapters(chapters) : Promise.resolve(),
966
+ loadChaptersFromUrl: (url: string) => p.loadChaptersFromUrl ? p.loadChaptersFromUrl(url) : Promise.resolve(),
967
+ getCurrentSegment: () => p.getCurrentSegment ? p.getCurrentSegment() : null,
968
+ skipToSegment: (segmentId: string) => p.skipToSegment && p.skipToSegment(segmentId),
969
+ getSegments: () => p.getSegments ? p.getSegments() : [],
970
+ updateChapterConfig: (config: any) => p.updateChapterConfig && p.updateChapterConfig(config),
971
+ hasChapters: () => p.hasChapters ? p.hasChapters() : false,
972
+ getChapters: () => p.getChapters ? p.getChapters() : null,
973
+ getCoreChapters: () => p.getCoreChapters ? p.getCoreChapters() : [],
974
+ getCoreSegments: () => p.getCoreSegments ? p.getCoreSegments() : [],
975
+ getCurrentChapterInfo: () => p.getCurrentChapterInfo ? p.getCurrentChapterInfo() : null,
976
+ seekToChapter: (chapterId: string) => p.seekToChapter && p.seekToChapter(chapterId),
977
+ getNextChapter: () => p.getNextChapter ? p.getNextChapter() : null,
978
+ getPreviousChapter: () => p.getPreviousChapter ? p.getPreviousChapter() : null,
979
+ };
980
+ props.onChapterAPI(chapterAPI);
981
+ }
982
+
983
+ // Quality API
984
+ if (props.onQualityAPI) {
985
+ const qualityAPI: QualityAPI = {
986
+ getQualities: () => p.getQualities ? p.getQualities() : [],
987
+ getCurrentQuality: () => p.getCurrentQuality ? p.getCurrentQuality() : null,
988
+ setQuality: (index: number) => p.setQuality && p.setQuality(index),
989
+ setAutoQuality: (enabled: boolean) => p.setAutoQuality && p.setAutoQuality(enabled),
990
+ };
991
+ props.onQualityAPI(qualityAPI);
992
+ }
993
+
994
+ // EPG API
995
+ if (props.onEPGAPI) {
996
+ const epgAPI: EPGControlAPI = {
997
+ setEPGData: (data: any) => p.setEPGData && p.setEPGData(data),
998
+ showEPGButton: () => p.showEPGButton && p.showEPGButton(),
999
+ hideEPGButton: () => p.hideEPGButton && p.hideEPGButton(),
1000
+ isEPGButtonVisible: () => p.isEPGButtonVisible ? p.isEPGButtonVisible() : false,
1001
+ };
1002
+ props.onEPGAPI(epgAPI);
1003
+ }
1004
+
1005
+ // UI Helper API
1006
+ if (props.onUIHelperAPI) {
1007
+ const uiAPI: UIHelperAPI = {
1008
+ focusPlayer: () => p.focusPlayer && p.focusPlayer(),
1009
+ showFullscreenTip: () => p.showFullscreenTip && p.showFullscreenTip(),
1010
+ triggerFullscreenButton: () => p.triggerFullscreenButton && p.triggerFullscreenButton(),
1011
+ showTemporaryMessage: (message: string) => p.showTemporaryMessage && p.showTemporaryMessage(message),
1012
+ showFullscreenInstructions: () => p.showFullscreenInstructions && p.showFullscreenInstructions(),
1013
+ enterFullscreenSynchronously: () => p.enterFullscreenSynchronously && p.enterFullscreenSynchronously(),
1014
+ };
1015
+ props.onUIHelperAPI(uiAPI);
1016
+ }
1017
+
1018
+ // Fullscreen API
1019
+ if (props.onFullscreenAPI) {
1020
+ const fullscreenAPI: FullscreenAPI = {
1021
+ enterFullscreen: () => p.enterFullscreen ? p.enterFullscreen() : Promise.resolve(),
1022
+ exitFullscreen: () => p.exitFullscreen ? p.exitFullscreen() : Promise.resolve(),
1023
+ toggleFullscreen: () => p.toggleFullscreen ? p.toggleFullscreen() : Promise.resolve(),
1024
+ enterPictureInPicture: () => p.enterPictureInPicture ? p.enterPictureInPicture() : Promise.resolve(),
1025
+ exitPictureInPicture: () => p.exitPictureInPicture ? p.exitPictureInPicture() : Promise.resolve(),
1026
+ };
1027
+ props.onFullscreenAPI(fullscreenAPI);
1028
+ }
1029
+
1030
+ // Playback API
1031
+ if (props.onPlaybackAPI) {
1032
+ const playbackAPI: PlaybackAPI = {
1033
+ play: () => p.play ? p.play() : Promise.resolve(),
1034
+ pause: () => p.pause && p.pause(),
1035
+ requestPause: () => p.requestPause && p.requestPause(),
1036
+ seek: (time: number) => p.seek && p.seek(time),
1037
+ setVolume: (level: number) => p.setVolume && p.setVolume(level),
1038
+ mute: () => p.mute && p.mute(),
1039
+ unmute: () => p.unmute && p.unmute(),
1040
+ toggleMute: () => p.toggleMute && p.toggleMute(),
1041
+ setPlaybackRate: (rate: number) => p.setPlaybackRate && p.setPlaybackRate(rate),
1042
+ getPlaybackRate: () => p.getPlaybackRate ? p.getPlaybackRate() : 1,
1043
+ getCurrentTime: () => p.getCurrentTime ? p.getCurrentTime() : 0,
1044
+ getDuration: () => p.getDuration ? p.getDuration() : 0,
1045
+ getState: () => p.getState ? p.getState() : {},
1046
+ };
1047
+ props.onPlaybackAPI(playbackAPI);
1048
+ }
1049
+ }, [
1050
+ props.onChapterAPI,
1051
+ props.onQualityAPI,
1052
+ props.onEPGAPI,
1053
+ props.onUIHelperAPI,
1054
+ props.onFullscreenAPI,
1055
+ props.onPlaybackAPI,
1056
+ ]);
1057
+
1058
+ // Helper function to apply settings scrollbar configuration
1059
+ const applySettingsScrollbar = useCallback((player: WebPlayer, config: NonNullable<typeof props.settingsScrollbar>) => {
1060
+ const p = player as any;
1061
+
1062
+ // Apply scrollbar style
1063
+ if (config.style && typeof p.setSettingsScrollbarStyle === 'function') {
1064
+ p.setSettingsScrollbarStyle(config.style);
1065
+ }
1066
+
1067
+ // Apply scrollbar config (width and intensity)
1068
+ if ((config.widthPx !== undefined || config.intensity !== undefined) &&
1069
+ typeof p.setSettingsScrollbarConfig === 'function') {
1070
+ p.setSettingsScrollbarConfig({
1071
+ widthPx: config.widthPx,
1072
+ intensity: config.intensity,
1073
+ });
1074
+ }
1075
+ }, []);
1076
+
1077
+ // Apply settings scrollbar changes at runtime
1078
+ useEffect(() => {
1079
+ const p = playerRef.current as any;
1080
+ if (p && props.settingsScrollbar && playerReady) {
1081
+ applySettingsScrollbar(p, props.settingsScrollbar);
1082
+ }
1083
+ }, [JSON.stringify(props.settingsScrollbar), playerReady, applySettingsScrollbar]);
1084
+
1085
+ // Re-expose APIs when player is ready (handles hot reloading)
1086
+ useEffect(() => {
1087
+ if (playerRef.current && playerReady) {
1088
+ exposeAPIsToParent(playerRef.current);
1089
+ }
1090
+ }, [playerReady, exposeAPIsToParent]);
725
1091
 
726
1092
  // Update free preview duration at runtime without full re-init
727
1093
  useEffect(() => {
@@ -789,7 +1155,24 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
789
1155
  ref={containerRef}
790
1156
  className={`uvf-responsive-container ${props.className || ''}`}
791
1157
  style={responsiveStyle}
792
- />
1158
+ >
1159
+ {/* Google Ads Container - positioned over the video */}
1160
+ {props.googleAds && (
1161
+ <div
1162
+ ref={adContainerRef}
1163
+ className="uvf-ad-container"
1164
+ style={{
1165
+ position: 'absolute',
1166
+ top: 0,
1167
+ left: 0,
1168
+ width: '100%',
1169
+ height: '100%',
1170
+ zIndex: 1000,
1171
+ pointerEvents: 'none',
1172
+ }}
1173
+ />
1174
+ )}
1175
+ </div>
793
1176
 
794
1177
  {/* EPG Overlay */}
795
1178
  {props.epg && EPGOverlay && epgComponentLoaded && (
@@ -0,0 +1,196 @@
1
+ import React, { useState } from 'react';
2
+ import { WebPlayerView } from '../WebPlayerView';
3
+
4
+ /**
5
+ * Example: Google Ads Integration with WebPlayerView
6
+ *
7
+ * This example demonstrates how to integrate Google IMA ads
8
+ * (pre-roll, mid-roll, post-roll, and companion ads) with the video player.
9
+ */
10
+
11
+ export const GoogleAdsExample: React.FC = () => {
12
+ const [adEvents, setAdEvents] = useState<string[]>([]);
13
+
14
+ const logAdEvent = (event: string) => {
15
+ console.log(`[Ad Event] ${event}`);
16
+ setAdEvents(prev => [...prev, `${new Date().toLocaleTimeString()}: ${event}`]);
17
+ };
18
+
19
+ return (
20
+ <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
21
+ <div style={{ flex: 1, position: 'relative' }}>
22
+ <WebPlayerView
23
+ // Video source
24
+ url="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
25
+ type="mp4"
26
+
27
+ // Basic player config
28
+ autoPlay={false}
29
+ controls={true}
30
+
31
+ // Google Ads Configuration
32
+ googleAds={{
33
+ // VAST/VMAP ad tag URL from your ad server
34
+ // This example uses Google's IMA test tag
35
+ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_ad_samples&sz=640x480&cust_params=sample_ct%3Dlinear&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=',
36
+
37
+ // Optional: Specific mid-roll times (in seconds)
38
+ // If not provided, VMAP schedule from ad server is used
39
+ midrollTimes: [30, 60, 120],
40
+
41
+ // Optional: Companion ads (sidebar/banner ads)
42
+ companionAdSlots: [
43
+ {
44
+ containerId: 'companion-ad-300x250',
45
+ width: 300,
46
+ height: 250,
47
+ },
48
+ {
49
+ containerId: 'companion-ad-728x90',
50
+ width: 728,
51
+ height: 90,
52
+ },
53
+ ],
54
+
55
+ // Ad event callbacks
56
+ onAdStart: () => {
57
+ logAdEvent('Ad Started');
58
+ },
59
+
60
+ onAdEnd: () => {
61
+ logAdEvent('Ad Ended - Resuming Content');
62
+ },
63
+
64
+ onAdError: (error: any) => {
65
+ logAdEvent(`Ad Error: ${error?.getMessage?.() || error}`);
66
+ },
67
+
68
+ onAllAdsComplete: () => {
69
+ logAdEvent('All Ads Completed');
70
+ },
71
+ }}
72
+
73
+ // Video metadata
74
+ metadata={{
75
+ title: 'Big Buck Bunny with Google Ads',
76
+ description: 'Demo video with pre-roll, mid-roll, and post-roll ads',
77
+ }}
78
+
79
+ // Player callbacks
80
+ onReady={(player) => {
81
+ console.log('Player ready with Google Ads integration');
82
+ }}
83
+
84
+ onError={(error) => {
85
+ console.error('Player error:', error);
86
+ }}
87
+ />
88
+ </div>
89
+
90
+ {/* Companion Ad Slots */}
91
+ <div style={{
92
+ display: 'flex',
93
+ justifyContent: 'center',
94
+ gap: '20px',
95
+ padding: '20px',
96
+ backgroundColor: '#f5f5f5',
97
+ }}>
98
+ {/* 300x250 Companion Ad */}
99
+ <div
100
+ id="companion-ad-300x250"
101
+ style={{
102
+ width: '300px',
103
+ height: '250px',
104
+ border: '1px solid #ddd',
105
+ backgroundColor: '#fff',
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ justifyContent: 'center',
109
+ color: '#999',
110
+ }}
111
+ >
112
+ 300x250 Companion Ad
113
+ </div>
114
+
115
+ {/* 728x90 Companion Ad */}
116
+ <div
117
+ id="companion-ad-728x90"
118
+ style={{
119
+ width: '728px',
120
+ height: '90px',
121
+ border: '1px solid #ddd',
122
+ backgroundColor: '#fff',
123
+ display: 'flex',
124
+ alignItems: 'center',
125
+ justifyContent: 'center',
126
+ color: '#999',
127
+ }}
128
+ >
129
+ 728x90 Companion Ad
130
+ </div>
131
+ </div>
132
+
133
+ {/* Ad Event Log */}
134
+ <div style={{
135
+ padding: '20px',
136
+ backgroundColor: '#f9f9f9',
137
+ borderTop: '1px solid #ddd',
138
+ maxHeight: '200px',
139
+ overflowY: 'auto',
140
+ }}>
141
+ <h3 style={{ margin: '0 0 10px 0', fontSize: '16px' }}>Ad Events Log:</h3>
142
+ {adEvents.length === 0 ? (
143
+ <p style={{ color: '#999', margin: 0 }}>No ad events yet. Play the video to see ads.</p>
144
+ ) : (
145
+ <ul style={{ margin: 0, padding: '0 0 0 20px' }}>
146
+ {adEvents.map((event, index) => (
147
+ <li key={index} style={{ fontSize: '14px', marginBottom: '5px' }}>
148
+ {event}
149
+ </li>
150
+ ))}
151
+ </ul>
152
+ )}
153
+ </div>
154
+ </div>
155
+ );
156
+ };
157
+
158
+ /**
159
+ * Usage Notes:
160
+ *
161
+ * 1. Ad Tag URL:
162
+ * - Use VAST or VMAP ad tag from your ad server (e.g., Google Ad Manager, SpotX)
163
+ * - The example uses Google's IMA test tag for demonstration
164
+ * - VAST: Single ad unit
165
+ * - VMAP: Ad schedule with pre/mid/post-rolls
166
+ *
167
+ * 2. Ad Types Supported:
168
+ * - Pre-roll: Plays before video content
169
+ * - Mid-roll: Plays during video at specified times
170
+ * - Post-roll: Plays after video ends
171
+ * - Overlay ads: Non-linear ads on video
172
+ * - Companion ads: Banner/sidebar ads
173
+ * - Skippable & non-skippable ads
174
+ *
175
+ * 3. Mid-roll Configuration:
176
+ * - Specify times in seconds: [30, 60, 120]
177
+ * - Or use VMAP schedule from ad server
178
+ *
179
+ * 4. Companion Ads:
180
+ * - Create HTML containers with specific IDs
181
+ * - Specify dimensions in config
182
+ * - Ads will be automatically inserted
183
+ *
184
+ * 5. Ad Events:
185
+ * - onAdStart: When ad begins playing
186
+ * - onAdEnd: When ad completes
187
+ * - onAdError: When ad fails to load/play
188
+ * - onAllAdsComplete: When all ads in pod complete
189
+ *
190
+ * 6. Testing:
191
+ * - Use Google's test tags: https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags
192
+ * - Test different ad formats and scenarios
193
+ * - Handle errors gracefully (network, ad blocker, etc.)
194
+ */
195
+
196
+ export default GoogleAdsExample;