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.
- package/package.json +1 -1
- package/packages/web/dist/WebPlayer.d.ts +7 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +107 -0
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +1 -2
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +12 -16
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/dist/react/components/FlashNewsTicker.d.ts.map +1 -1
- package/packages/web/dist/react/components/FlashNewsTicker.js +2 -17
- package/packages/web/dist/react/components/FlashNewsTicker.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +142 -3
- package/packages/web/src/react/WebPlayerView.tsx +13 -31
- package/packages/web/src/react/components/FlashNewsTicker.tsx +0 -220
|
@@ -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
|
|
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;
|