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.
@@ -20,6 +20,76 @@ const loadEPGComponents = async () => {
20
20
  return EPGOverlay;
21
21
  };
22
22
 
23
+ // Chapter API interface exposed to parent component
24
+ export interface ChapterAPI {
25
+ loadChapters: (chapters: any) => Promise<void>;
26
+ loadChaptersFromUrl: (url: string) => Promise<void>;
27
+ getCurrentSegment: () => any | null;
28
+ skipToSegment: (segmentId: string) => void;
29
+ getSegments: () => any[];
30
+ updateChapterConfig: (config: any) => void;
31
+ hasChapters: () => boolean;
32
+ getChapters: () => any | null;
33
+ getCoreChapters: () => any[];
34
+ getCoreSegments: () => any[];
35
+ getCurrentChapterInfo: () => any | null;
36
+ seekToChapter: (chapterId: string) => void;
37
+ getNextChapter: () => any | null;
38
+ getPreviousChapter: () => any | null;
39
+ }
40
+
41
+ // Quality control API
42
+ export interface QualityAPI {
43
+ getQualities: () => any[];
44
+ getCurrentQuality: () => any | null;
45
+ setQuality: (index: number) => void;
46
+ setAutoQuality: (enabled: boolean) => void;
47
+ }
48
+
49
+ // EPG API interface
50
+ export interface EPGControlAPI {
51
+ setEPGData: (data: any) => void;
52
+ showEPGButton: () => void;
53
+ hideEPGButton: () => void;
54
+ isEPGButtonVisible: () => boolean;
55
+ }
56
+
57
+ // UI Helper API
58
+ export interface UIHelperAPI {
59
+ focusPlayer: () => void;
60
+ showFullscreenTip: () => void;
61
+ triggerFullscreenButton: () => void;
62
+ showTemporaryMessage: (message: string) => void;
63
+ showFullscreenInstructions: () => void;
64
+ enterFullscreenSynchronously: () => void;
65
+ }
66
+
67
+ // Fullscreen API
68
+ export interface FullscreenAPI {
69
+ enterFullscreen: () => Promise<void>;
70
+ exitFullscreen: () => Promise<void>;
71
+ toggleFullscreen: () => Promise<void>;
72
+ enterPictureInPicture: () => Promise<void>;
73
+ exitPictureInPicture: () => Promise<void>;
74
+ }
75
+
76
+ // Playback control API
77
+ export interface PlaybackAPI {
78
+ play: () => Promise<void>;
79
+ pause: () => void;
80
+ requestPause: () => void;
81
+ seek: (time: number) => void;
82
+ setVolume: (level: number) => void;
83
+ mute: () => void;
84
+ unmute: () => void;
85
+ toggleMute: () => void;
86
+ setPlaybackRate: (rate: number) => void;
87
+ getPlaybackRate: () => number;
88
+ getCurrentTime: () => number;
89
+ getDuration: () => number;
90
+ getState: () => any;
91
+ }
92
+
23
93
  export type WebPlayerViewProps = {
24
94
  // Player config
25
95
  autoPlay?: boolean;
@@ -144,9 +214,45 @@ export type WebPlayerViewProps = {
144
214
  };
145
215
  };
146
216
 
217
+ // Settings customization
218
+ settingsScrollbar?: {
219
+ style?: 'default' | 'compact' | 'overlay';
220
+ widthPx?: number;
221
+ intensity?: number;
222
+ };
223
+
224
+ // Auto-focus player on mount
225
+ autoFocusPlayer?: boolean;
226
+
227
+ // Show fullscreen tip on mount
228
+ showFullscreenTipOnMount?: boolean;
229
+
230
+ // Player instance ref (for imperative control)
231
+ playerRef?: React.RefObject<WebPlayer>;
232
+
233
+ // API callbacks - expose imperative APIs to parent
234
+ onChapterAPI?: (api: ChapterAPI) => void;
235
+ onQualityAPI?: (api: QualityAPI) => void;
236
+ onEPGAPI?: (api: EPGControlAPI) => void;
237
+ onUIHelperAPI?: (api: UIHelperAPI) => void;
238
+ onFullscreenAPI?: (api: FullscreenAPI) => void;
239
+ onPlaybackAPI?: (api: PlaybackAPI) => void;
240
+
147
241
  // Callbacks
148
242
  onReady?: (player: WebPlayer) => void;
149
243
  onError?: (error: unknown) => void;
244
+
245
+ // Additional player event callbacks
246
+ onPlay?: () => void;
247
+ onPause?: () => void;
248
+ onEnded?: () => void;
249
+ onTimeUpdate?: (data: { currentTime: number; duration: number }) => void;
250
+ onProgress?: (data: { buffered: number }) => void;
251
+ onVolumeChange?: (data: { volume: number; muted: boolean }) => void;
252
+ onQualityChange?: (quality: any) => void;
253
+ onBuffering?: (isBuffering: boolean) => void;
254
+ onFullscreenChange?: (isFullscreen: boolean) => void;
255
+ onPictureInPictureChange?: (isPiP: boolean) => void;
150
256
 
151
257
  // EPG (Electronic Program Guide) support
152
258
  epg?: EPGData;
@@ -258,7 +364,9 @@ export type WebPlayerViewProps = {
258
364
 
259
365
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
260
366
  const containerRef = useRef<HTMLDivElement>(null);
261
- const playerRef = useRef<WebPlayer | null>(null);
367
+ const internalPlayerRef = useRef<WebPlayer | null>(null);
368
+ // Use external ref if provided, otherwise use internal
369
+ const playerRef = props.playerRef || internalPlayerRef;
262
370
  const [dimensions, setDimensions] = useState({
263
371
  width: typeof window !== 'undefined' ? window.innerWidth : 1920,
264
372
  height: typeof window !== 'undefined' ? window.innerHeight : 1080,
@@ -479,6 +587,9 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
479
587
 
480
588
  const player = new WebPlayer();
481
589
  playerRef.current = player;
590
+
591
+ // Expose all APIs to parent component via callbacks
592
+ exposeAPIsToParent(player);
482
593
 
483
594
  // Optionally load Google Cast sender SDK
484
595
  if (props.cast) {
@@ -626,6 +737,21 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
626
737
  if (!cancelled) {
627
738
  setPlayerReady(true);
628
739
 
740
+ // Apply settings scrollbar configuration if provided
741
+ if (props.settingsScrollbar) {
742
+ applySettingsScrollbar(player, props.settingsScrollbar);
743
+ }
744
+
745
+ // Auto-focus player if requested
746
+ if (props.autoFocusPlayer && typeof (player as any).focusPlayer === 'function') {
747
+ (player as any).focusPlayer();
748
+ }
749
+
750
+ // Show fullscreen tip if requested
751
+ if (props.showFullscreenTipOnMount && typeof (player as any).showFullscreenTip === 'function') {
752
+ (player as any).showFullscreenTip();
753
+ }
754
+
629
755
  // Set up EPG integration
630
756
  if (props.epg && typeof (player as any).setEPGData === 'function') {
631
757
  (player as any).setEPGData(props.epg);
@@ -678,6 +804,48 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
678
804
  (player as any).on('navigationCloseClicked', props.onNavigationCloseClicked);
679
805
  }
680
806
 
807
+ // Additional event listeners
808
+ if (props.onPlay && typeof (player as any).on === 'function') {
809
+ (player as any).on('onPlay', props.onPlay);
810
+ }
811
+ if (props.onPause && typeof (player as any).on === 'function') {
812
+ (player as any).on('onPause', props.onPause);
813
+ }
814
+ if (props.onEnded && typeof (player as any).on === 'function') {
815
+ (player as any).on('onEnded', props.onEnded);
816
+ }
817
+ if (props.onTimeUpdate && typeof (player as any).on === 'function') {
818
+ (player as any).on('onTimeUpdate', (currentTime: number) => {
819
+ props.onTimeUpdate?.({
820
+ currentTime,
821
+ duration: (player as any).getDuration ? (player as any).getDuration() : 0
822
+ });
823
+ });
824
+ }
825
+ if (props.onProgress && typeof (player as any).on === 'function') {
826
+ (player as any).on('onProgress', (buffered: number) => {
827
+ props.onProgress?.({ buffered });
828
+ });
829
+ }
830
+ if (props.onVolumeChange && typeof (player as any).on === 'function') {
831
+ (player as any).on('onVolumeChanged', (volume: number) => {
832
+ const state = (player as any).getState ? (player as any).getState() : {};
833
+ props.onVolumeChange?.({ volume, muted: state.isMuted || false });
834
+ });
835
+ }
836
+ if (props.onQualityChange && typeof (player as any).on === 'function') {
837
+ (player as any).on('onQualityChanged', props.onQualityChange);
838
+ }
839
+ if (props.onBuffering && typeof (player as any).on === 'function') {
840
+ (player as any).on('onBuffering', props.onBuffering);
841
+ }
842
+ if (props.onFullscreenChange && typeof (player as any).on === 'function') {
843
+ (player as any).on('onFullscreenChanged', props.onFullscreenChange);
844
+ }
845
+ if (props.onPictureInPictureChange && typeof (player as any).on === 'function') {
846
+ (player as any).on('onPictureInPicturechange', props.onPictureInPictureChange);
847
+ }
848
+
681
849
  props.onReady?.(player);
682
850
  }
683
851
  } catch (err) {
@@ -721,7 +889,141 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
721
889
  props.showFrameworkBranding,
722
890
  JSON.stringify(props.watermark),
723
891
  JSON.stringify(props.navigation),
724
- ])
892
+ ]);
893
+
894
+ // Helper function to expose all APIs to parent
895
+ const exposeAPIsToParent = useCallback((player: WebPlayer) => {
896
+ const p = player as any;
897
+
898
+ // Chapter API
899
+ if (props.onChapterAPI) {
900
+ const chapterAPI: ChapterAPI = {
901
+ loadChapters: (chapters: any) => p.loadChapters ? p.loadChapters(chapters) : Promise.resolve(),
902
+ loadChaptersFromUrl: (url: string) => p.loadChaptersFromUrl ? p.loadChaptersFromUrl(url) : Promise.resolve(),
903
+ getCurrentSegment: () => p.getCurrentSegment ? p.getCurrentSegment() : null,
904
+ skipToSegment: (segmentId: string) => p.skipToSegment && p.skipToSegment(segmentId),
905
+ getSegments: () => p.getSegments ? p.getSegments() : [],
906
+ updateChapterConfig: (config: any) => p.updateChapterConfig && p.updateChapterConfig(config),
907
+ hasChapters: () => p.hasChapters ? p.hasChapters() : false,
908
+ getChapters: () => p.getChapters ? p.getChapters() : null,
909
+ getCoreChapters: () => p.getCoreChapters ? p.getCoreChapters() : [],
910
+ getCoreSegments: () => p.getCoreSegments ? p.getCoreSegments() : [],
911
+ getCurrentChapterInfo: () => p.getCurrentChapterInfo ? p.getCurrentChapterInfo() : null,
912
+ seekToChapter: (chapterId: string) => p.seekToChapter && p.seekToChapter(chapterId),
913
+ getNextChapter: () => p.getNextChapter ? p.getNextChapter() : null,
914
+ getPreviousChapter: () => p.getPreviousChapter ? p.getPreviousChapter() : null,
915
+ };
916
+ props.onChapterAPI(chapterAPI);
917
+ }
918
+
919
+ // Quality API
920
+ if (props.onQualityAPI) {
921
+ const qualityAPI: QualityAPI = {
922
+ getQualities: () => p.getQualities ? p.getQualities() : [],
923
+ getCurrentQuality: () => p.getCurrentQuality ? p.getCurrentQuality() : null,
924
+ setQuality: (index: number) => p.setQuality && p.setQuality(index),
925
+ setAutoQuality: (enabled: boolean) => p.setAutoQuality && p.setAutoQuality(enabled),
926
+ };
927
+ props.onQualityAPI(qualityAPI);
928
+ }
929
+
930
+ // EPG API
931
+ if (props.onEPGAPI) {
932
+ const epgAPI: EPGControlAPI = {
933
+ setEPGData: (data: any) => p.setEPGData && p.setEPGData(data),
934
+ showEPGButton: () => p.showEPGButton && p.showEPGButton(),
935
+ hideEPGButton: () => p.hideEPGButton && p.hideEPGButton(),
936
+ isEPGButtonVisible: () => p.isEPGButtonVisible ? p.isEPGButtonVisible() : false,
937
+ };
938
+ props.onEPGAPI(epgAPI);
939
+ }
940
+
941
+ // UI Helper API
942
+ if (props.onUIHelperAPI) {
943
+ const uiAPI: UIHelperAPI = {
944
+ focusPlayer: () => p.focusPlayer && p.focusPlayer(),
945
+ showFullscreenTip: () => p.showFullscreenTip && p.showFullscreenTip(),
946
+ triggerFullscreenButton: () => p.triggerFullscreenButton && p.triggerFullscreenButton(),
947
+ showTemporaryMessage: (message: string) => p.showTemporaryMessage && p.showTemporaryMessage(message),
948
+ showFullscreenInstructions: () => p.showFullscreenInstructions && p.showFullscreenInstructions(),
949
+ enterFullscreenSynchronously: () => p.enterFullscreenSynchronously && p.enterFullscreenSynchronously(),
950
+ };
951
+ props.onUIHelperAPI(uiAPI);
952
+ }
953
+
954
+ // Fullscreen API
955
+ if (props.onFullscreenAPI) {
956
+ const fullscreenAPI: FullscreenAPI = {
957
+ enterFullscreen: () => p.enterFullscreen ? p.enterFullscreen() : Promise.resolve(),
958
+ exitFullscreen: () => p.exitFullscreen ? p.exitFullscreen() : Promise.resolve(),
959
+ toggleFullscreen: () => p.toggleFullscreen ? p.toggleFullscreen() : Promise.resolve(),
960
+ enterPictureInPicture: () => p.enterPictureInPicture ? p.enterPictureInPicture() : Promise.resolve(),
961
+ exitPictureInPicture: () => p.exitPictureInPicture ? p.exitPictureInPicture() : Promise.resolve(),
962
+ };
963
+ props.onFullscreenAPI(fullscreenAPI);
964
+ }
965
+
966
+ // Playback API
967
+ if (props.onPlaybackAPI) {
968
+ const playbackAPI: PlaybackAPI = {
969
+ play: () => p.play ? p.play() : Promise.resolve(),
970
+ pause: () => p.pause && p.pause(),
971
+ requestPause: () => p.requestPause && p.requestPause(),
972
+ seek: (time: number) => p.seek && p.seek(time),
973
+ setVolume: (level: number) => p.setVolume && p.setVolume(level),
974
+ mute: () => p.mute && p.mute(),
975
+ unmute: () => p.unmute && p.unmute(),
976
+ toggleMute: () => p.toggleMute && p.toggleMute(),
977
+ setPlaybackRate: (rate: number) => p.setPlaybackRate && p.setPlaybackRate(rate),
978
+ getPlaybackRate: () => p.getPlaybackRate ? p.getPlaybackRate() : 1,
979
+ getCurrentTime: () => p.getCurrentTime ? p.getCurrentTime() : 0,
980
+ getDuration: () => p.getDuration ? p.getDuration() : 0,
981
+ getState: () => p.getState ? p.getState() : {},
982
+ };
983
+ props.onPlaybackAPI(playbackAPI);
984
+ }
985
+ }, [
986
+ props.onChapterAPI,
987
+ props.onQualityAPI,
988
+ props.onEPGAPI,
989
+ props.onUIHelperAPI,
990
+ props.onFullscreenAPI,
991
+ props.onPlaybackAPI,
992
+ ]);
993
+
994
+ // Helper function to apply settings scrollbar configuration
995
+ const applySettingsScrollbar = useCallback((player: WebPlayer, config: NonNullable<typeof props.settingsScrollbar>) => {
996
+ const p = player as any;
997
+
998
+ // Apply scrollbar style
999
+ if (config.style && typeof p.setSettingsScrollbarStyle === 'function') {
1000
+ p.setSettingsScrollbarStyle(config.style);
1001
+ }
1002
+
1003
+ // Apply scrollbar config (width and intensity)
1004
+ if ((config.widthPx !== undefined || config.intensity !== undefined) &&
1005
+ typeof p.setSettingsScrollbarConfig === 'function') {
1006
+ p.setSettingsScrollbarConfig({
1007
+ widthPx: config.widthPx,
1008
+ intensity: config.intensity,
1009
+ });
1010
+ }
1011
+ }, []);
1012
+
1013
+ // Apply settings scrollbar changes at runtime
1014
+ useEffect(() => {
1015
+ const p = playerRef.current as any;
1016
+ if (p && props.settingsScrollbar && playerReady) {
1017
+ applySettingsScrollbar(p, props.settingsScrollbar);
1018
+ }
1019
+ }, [JSON.stringify(props.settingsScrollbar), playerReady, applySettingsScrollbar]);
1020
+
1021
+ // Re-expose APIs when player is ready (handles hot reloading)
1022
+ useEffect(() => {
1023
+ if (playerRef.current && playerReady) {
1024
+ exposeAPIsToParent(playerRef.current);
1025
+ }
1026
+ }, [playerReady, exposeAPIsToParent]);
725
1027
 
726
1028
  // Update free preview duration at runtime without full re-init
727
1029
  useEffect(() => {