unified-video-framework 1.4.342 → 1.4.344

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.
@@ -7,11 +7,10 @@ import { GoogleAdsManager } from '../ads/GoogleAdsManager';
7
7
  // EPG imports - conditionally loaded
8
8
  import type { EPGData, EPGConfig, EPGProgram, EPGProgramRow } from './types/EPGTypes';
9
9
  import type { VCManifest, VCProduct, VCEvent } from './types/VideoCommerceTypes';
10
- import type { FlashNewsTickerConfig, FlashNewsTickerAPI } from './types/FlashNewsTickerTypes';
10
+ import type { FlashNewsTickerConfig } from './types/FlashNewsTickerTypes';
11
11
  import { useCommerceSync } from './hooks/useCommerceSync';
12
12
  import ProductBadge from './components/commerce/ProductBadge';
13
13
  import ProductPanel from './components/commerce/ProductPanel';
14
- import FlashNewsTicker from './components/FlashNewsTicker';
15
14
  let EPGOverlay: React.ComponentType<any> | null = null;
16
15
 
17
16
  // Lazy load EPG components to avoid bundle size impact when not used
@@ -433,7 +432,6 @@ export type WebPlayerViewProps = {
433
432
 
434
433
  // Flash News Ticker
435
434
  flashNewsTicker?: FlashNewsTickerConfig; // Flash news ticker configuration
436
- onFlashNewsTickerAPI?: (api: FlashNewsTickerAPI) => void; // Callback to receive ticker API
437
435
  };
438
436
 
439
437
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
@@ -441,7 +439,6 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
441
439
  const internalPlayerRef = useRef<WebPlayer | null>(null);
442
440
  // Use external ref if provided, otherwise use internal
443
441
  const playerRef = props.playerRef || internalPlayerRef;
444
- const flashNewsTickerAPIRef = useRef<FlashNewsTickerAPI | null>(null);
445
442
  const [dimensions, setDimensions] = useState({
446
443
  width: typeof window !== 'undefined' ? window.innerWidth : 1920,
447
444
  height: typeof window !== 'undefined' ? window.innerHeight : 1080,
@@ -1559,6 +1556,18 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1559
1556
  } catch (_) {}
1560
1557
  }, [JSON.stringify(props.playerTheme)]);
1561
1558
 
1559
+ // Update flash news ticker when config changes
1560
+ useEffect(() => {
1561
+ const p = playerRef.current as any;
1562
+ if (p && typeof p.setFlashNewsTicker === 'function' && props.flashNewsTicker) {
1563
+ try {
1564
+ p.setFlashNewsTicker(props.flashNewsTicker);
1565
+ } catch (err) {
1566
+ console.warn('[WebPlayerView] Failed to update flash news ticker:', err);
1567
+ }
1568
+ }
1569
+ }, [JSON.stringify(props.flashNewsTicker)]);
1570
+
1562
1571
  const responsiveStyle = getResponsiveDimensions();
1563
1572
 
1564
1573
  // Prepare EPG config with action handlers
@@ -1730,33 +1739,6 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1730
1739
  />
1731
1740
  )}
1732
1741
 
1733
- {/* Flash News Ticker Overlays */}
1734
- {props.flashNewsTicker?.enabled && (
1735
- <>
1736
- {(props.flashNewsTicker.position === 'top' ||
1737
- props.flashNewsTicker.position === 'both') && (
1738
- <FlashNewsTicker
1739
- config={{ ...props.flashNewsTicker, position: 'top' }}
1740
- onAPIReady={(api) => {
1741
- flashNewsTickerAPIRef.current = api;
1742
- props.onFlashNewsTickerAPI?.(api);
1743
- }}
1744
- />
1745
- )}
1746
- {(props.flashNewsTicker.position === 'bottom' ||
1747
- props.flashNewsTicker.position === 'both') && (
1748
- <FlashNewsTicker
1749
- config={{ ...props.flashNewsTicker, position: 'bottom' }}
1750
- onAPIReady={(api) => {
1751
- if (!flashNewsTickerAPIRef.current) {
1752
- flashNewsTickerAPIRef.current = api;
1753
- props.onFlashNewsTickerAPI?.(api);
1754
- }
1755
- }}
1756
- />
1757
- )}
1758
- </>
1759
- )}
1760
1742
  </>
1761
1743
  );
1762
1744
  };
@@ -1,220 +0,0 @@
1
- import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import type {
3
- FlashNewsTickerConfig,
4
- FlashNewsTickerItem,
5
- FlashNewsTickerAPI,
6
- } from '../types/FlashNewsTickerTypes';
7
-
8
- interface Props {
9
- config: FlashNewsTickerConfig;
10
- onAPIReady?: (api: FlashNewsTickerAPI) => void;
11
- }
12
-
13
- export const FlashNewsTicker: React.FC<Props> = ({ config, onAPIReady }) => {
14
- // State
15
- const [visible, setVisible] = useState(config.enabled ?? true);
16
- const [items, setItems] = useState<FlashNewsTickerItem[]>(config.items || []);
17
- const [isPaused, setIsPaused] = useState(false);
18
- const [contentWidth, setContentWidth] = useState(0);
19
- const [controlsHeight, setControlsHeight] = useState(60);
20
-
21
- // Refs
22
- const contentRef = useRef<HTMLDivElement>(null);
23
- const apiRef = useRef<FlashNewsTickerAPI | null>(null);
24
- const resizeObserverRef = useRef<ResizeObserver | null>(null);
25
-
26
- // Merge config with defaults
27
- const mergedConfig = useMemo(
28
- () => ({
29
- position: 'bottom' as 'top' | 'bottom' | 'both',
30
- height: 40,
31
- backgroundColor: 'rgba(0, 0, 0, 0.7)',
32
- textColor: '#ffffff',
33
- fontSize: 14,
34
- fontWeight: 600,
35
- speed: 50,
36
- gap: 100,
37
- loop: true,
38
- separator: ' • ',
39
- topOffset: 10,
40
- bottomOffset: 10,
41
- ...config,
42
- }),
43
- [config]
44
- );
45
-
46
- // Calculate animation duration based on content width
47
- const animationDuration = useMemo(() => {
48
- return contentWidth > 0 ? contentWidth / mergedConfig.speed : 20;
49
- }, [contentWidth, mergedConfig.speed]);
50
-
51
- // Filter active items by time range and priority
52
- const activeItems = useMemo(() => {
53
- const now = Date.now();
54
- return items
55
- .filter((item) => {
56
- if (item.startTime && now < item.startTime) return false;
57
- if (item.endTime && now > item.endTime) return false;
58
- return true;
59
- })
60
- .sort((a, b) => (b.priority || 0) - (a.priority || 0));
61
- }, [items]);
62
-
63
- // Measure content width when items change
64
- useEffect(() => {
65
- if (!contentRef.current) return;
66
-
67
- const measureWidth = () => {
68
- if (contentRef.current) {
69
- setContentWidth(contentRef.current.scrollWidth);
70
- }
71
- };
72
-
73
- // Initial measurement
74
- measureWidth();
75
-
76
- // Set up ResizeObserver for dynamic resizing
77
- if (typeof ResizeObserver !== 'undefined') {
78
- resizeObserverRef.current = new ResizeObserver(() => {
79
- measureWidth();
80
- });
81
- resizeObserverRef.current.observe(contentRef.current);
82
- }
83
-
84
- return () => {
85
- if (resizeObserverRef.current) {
86
- resizeObserverRef.current.disconnect();
87
- }
88
- };
89
- }, [activeItems]);
90
-
91
- // Update items when config changes
92
- useEffect(() => {
93
- setItems(config.items || []);
94
- }, [config.items]);
95
-
96
- // Detect controls height dynamically
97
- useEffect(() => {
98
- const detectControlsHeight = () => {
99
- // Try to find the video controls element
100
- const controls = document.querySelector('.uvf-controls, .video-controls, [class*="controls"]') as HTMLElement;
101
- if (controls) {
102
- const height = controls.offsetHeight;
103
- if (height > 0) {
104
- setControlsHeight(height);
105
- }
106
- }
107
- };
108
-
109
- // Initial detection
110
- detectControlsHeight();
111
-
112
- // Re-detect periodically in case controls appear later
113
- const interval = setInterval(detectControlsHeight, 1000);
114
-
115
- return () => clearInterval(interval);
116
- }, []);
117
-
118
- // Create and expose API
119
- useEffect(() => {
120
- const api: FlashNewsTickerAPI = {
121
- show: () => setVisible(true),
122
- hide: () => setVisible(false),
123
- isVisible: () => visible,
124
- updateItems: (newItems) => setItems(newItems),
125
- addItem: (item) => setItems((prev) => [...prev, item]),
126
- removeItem: (id) => setItems((prev) => prev.filter((i) => i.id !== id)),
127
- clearItems: () => setItems([]),
128
- updateConfig: () => {
129
- // Config is controlled via props, no local update needed
130
- },
131
- pause: () => setIsPaused(true),
132
- resume: () => setIsPaused(false),
133
- isPaused: () => isPaused,
134
- };
135
-
136
- apiRef.current = api;
137
- onAPIReady?.(api);
138
- }, [visible, isPaused, onAPIReady]);
139
-
140
- // Don't render if hidden or no items
141
- if (!visible || activeItems.length === 0) return null;
142
-
143
- // Responsive adjustments
144
- const isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;
145
- const height = isMobile ? 32 : mergedConfig.height;
146
- const fontSize = isMobile ? 12 : mergedConfig.fontSize;
147
-
148
- // Position styles - for bottom position, add controls height to the offset
149
- const positionStyles =
150
- mergedConfig.position === 'top'
151
- ? { top: mergedConfig.topOffset }
152
- : { bottom: (mergedConfig.bottomOffset || 10) + controlsHeight };
153
-
154
- return (
155
- <>
156
- <div
157
- className={`uvf-flash-news-ticker ticker-${mergedConfig.position}`}
158
- style={{
159
- position: 'fixed',
160
- left: 0,
161
- right: 0,
162
- height: `${height}px`,
163
- backgroundColor: mergedConfig.backgroundColor,
164
- zIndex: 275,
165
- overflow: 'hidden',
166
- pointerEvents: 'none',
167
- display: 'flex',
168
- alignItems: 'center',
169
- ...positionStyles,
170
- }}
171
- >
172
- <div
173
- ref={contentRef}
174
- style={{
175
- display: 'flex',
176
- whiteSpace: 'nowrap',
177
- animation:
178
- isPaused || !mergedConfig.loop
179
- ? 'none'
180
- : `ticker-scroll ${animationDuration}s linear infinite`,
181
- willChange: 'transform',
182
- }}
183
- >
184
- {/* Render items twice for seamless loop */}
185
- {[...activeItems, ...activeItems].map((item, idx) => (
186
- <span
187
- key={`${item.id}-${idx}`}
188
- style={{
189
- color: mergedConfig.textColor,
190
- fontSize: `${fontSize}px`,
191
- fontWeight: mergedConfig.fontWeight,
192
- marginRight: `${mergedConfig.gap}px`,
193
- }}
194
- >
195
- {item.text}
196
- {mergedConfig.separator && idx < activeItems.length * 2 - 1 && (
197
- <span style={{ opacity: 0.5, margin: '0 8px' }}>
198
- {mergedConfig.separator}
199
- </span>
200
- )}
201
- </span>
202
- ))}
203
- </div>
204
- </div>
205
-
206
- <style>{`
207
- @keyframes ticker-scroll {
208
- 0% {
209
- transform: translateX(0);
210
- }
211
- 100% {
212
- transform: translateX(-50%);
213
- }
214
- }
215
- `}</style>
216
- </>
217
- );
218
- };
219
-
220
- export default FlashNewsTicker;