unified-video-framework 1.4.343 → 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/src/WebPlayer.ts +142 -3
- package/packages/web/src/react/WebPlayerView.tsx +13 -31
- package/packages/web/src/react/components/FlashNewsTicker.tsx +0 -197
|
@@ -1,197 +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
|
-
|
|
20
|
-
// Refs
|
|
21
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
22
|
-
const apiRef = useRef<FlashNewsTickerAPI | null>(null);
|
|
23
|
-
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
24
|
-
|
|
25
|
-
// Merge config with defaults
|
|
26
|
-
const mergedConfig = useMemo(
|
|
27
|
-
() => ({
|
|
28
|
-
position: 'bottom' as 'top' | 'bottom' | 'both',
|
|
29
|
-
height: 40,
|
|
30
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
31
|
-
textColor: '#ffffff',
|
|
32
|
-
fontSize: 14,
|
|
33
|
-
fontWeight: 600,
|
|
34
|
-
speed: 50,
|
|
35
|
-
gap: 100,
|
|
36
|
-
loop: true,
|
|
37
|
-
separator: ' • ',
|
|
38
|
-
topOffset: 10,
|
|
39
|
-
bottomOffset: 10,
|
|
40
|
-
...config,
|
|
41
|
-
}),
|
|
42
|
-
[config]
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
// Calculate animation duration based on content width
|
|
46
|
-
const animationDuration = useMemo(() => {
|
|
47
|
-
return contentWidth > 0 ? contentWidth / mergedConfig.speed : 20;
|
|
48
|
-
}, [contentWidth, mergedConfig.speed]);
|
|
49
|
-
|
|
50
|
-
// Filter active items by time range and priority
|
|
51
|
-
const activeItems = useMemo(() => {
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
return items
|
|
54
|
-
.filter((item) => {
|
|
55
|
-
if (item.startTime && now < item.startTime) return false;
|
|
56
|
-
if (item.endTime && now > item.endTime) return false;
|
|
57
|
-
return true;
|
|
58
|
-
})
|
|
59
|
-
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
60
|
-
}, [items]);
|
|
61
|
-
|
|
62
|
-
// Measure content width when items change
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (!contentRef.current) return;
|
|
65
|
-
|
|
66
|
-
const measureWidth = () => {
|
|
67
|
-
if (contentRef.current) {
|
|
68
|
-
setContentWidth(contentRef.current.scrollWidth);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Initial measurement
|
|
73
|
-
measureWidth();
|
|
74
|
-
|
|
75
|
-
// Set up ResizeObserver for dynamic resizing
|
|
76
|
-
if (typeof ResizeObserver !== 'undefined') {
|
|
77
|
-
resizeObserverRef.current = new ResizeObserver(() => {
|
|
78
|
-
measureWidth();
|
|
79
|
-
});
|
|
80
|
-
resizeObserverRef.current.observe(contentRef.current);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return () => {
|
|
84
|
-
if (resizeObserverRef.current) {
|
|
85
|
-
resizeObserverRef.current.disconnect();
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}, [activeItems]);
|
|
89
|
-
|
|
90
|
-
// Update items when config changes
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
setItems(config.items || []);
|
|
93
|
-
}, [config.items]);
|
|
94
|
-
|
|
95
|
-
// Create and expose API
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
const api: FlashNewsTickerAPI = {
|
|
98
|
-
show: () => setVisible(true),
|
|
99
|
-
hide: () => setVisible(false),
|
|
100
|
-
isVisible: () => visible,
|
|
101
|
-
updateItems: (newItems) => setItems(newItems),
|
|
102
|
-
addItem: (item) => setItems((prev) => [...prev, item]),
|
|
103
|
-
removeItem: (id) => setItems((prev) => prev.filter((i) => i.id !== id)),
|
|
104
|
-
clearItems: () => setItems([]),
|
|
105
|
-
updateConfig: () => {
|
|
106
|
-
// Config is controlled via props, no local update needed
|
|
107
|
-
},
|
|
108
|
-
pause: () => setIsPaused(true),
|
|
109
|
-
resume: () => setIsPaused(false),
|
|
110
|
-
isPaused: () => isPaused,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
apiRef.current = api;
|
|
114
|
-
onAPIReady?.(api);
|
|
115
|
-
}, [visible, isPaused, onAPIReady]);
|
|
116
|
-
|
|
117
|
-
// Don't render if hidden or no items
|
|
118
|
-
if (!visible || activeItems.length === 0) return null;
|
|
119
|
-
|
|
120
|
-
// Responsive adjustments
|
|
121
|
-
const isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;
|
|
122
|
-
const height = isMobile ? 32 : mergedConfig.height;
|
|
123
|
-
const fontSize = isMobile ? 12 : mergedConfig.fontSize;
|
|
124
|
-
|
|
125
|
-
// Position styles - overlay on video, just above where controls appear
|
|
126
|
-
const positionStyles =
|
|
127
|
-
mergedConfig.position === 'top'
|
|
128
|
-
? { top: mergedConfig.topOffset }
|
|
129
|
-
: { bottom: mergedConfig.bottomOffset || 10 };
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<>
|
|
133
|
-
<div
|
|
134
|
-
className={`uvf-flash-news-ticker ticker-${mergedConfig.position}`}
|
|
135
|
-
style={{
|
|
136
|
-
position: 'fixed',
|
|
137
|
-
left: 0,
|
|
138
|
-
right: 0,
|
|
139
|
-
height: `${height}px`,
|
|
140
|
-
backgroundColor: mergedConfig.backgroundColor,
|
|
141
|
-
zIndex: 90,
|
|
142
|
-
overflow: 'hidden',
|
|
143
|
-
pointerEvents: 'none',
|
|
144
|
-
display: 'flex',
|
|
145
|
-
alignItems: 'center',
|
|
146
|
-
...positionStyles,
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
<div
|
|
150
|
-
ref={contentRef}
|
|
151
|
-
style={{
|
|
152
|
-
display: 'flex',
|
|
153
|
-
whiteSpace: 'nowrap',
|
|
154
|
-
animation:
|
|
155
|
-
isPaused || !mergedConfig.loop
|
|
156
|
-
? 'none'
|
|
157
|
-
: `ticker-scroll ${animationDuration}s linear infinite`,
|
|
158
|
-
willChange: 'transform',
|
|
159
|
-
}}
|
|
160
|
-
>
|
|
161
|
-
{/* Render items twice for seamless loop */}
|
|
162
|
-
{[...activeItems, ...activeItems].map((item, idx) => (
|
|
163
|
-
<span
|
|
164
|
-
key={`${item.id}-${idx}`}
|
|
165
|
-
style={{
|
|
166
|
-
color: mergedConfig.textColor,
|
|
167
|
-
fontSize: `${fontSize}px`,
|
|
168
|
-
fontWeight: mergedConfig.fontWeight,
|
|
169
|
-
marginRight: `${mergedConfig.gap}px`,
|
|
170
|
-
}}
|
|
171
|
-
>
|
|
172
|
-
{item.text}
|
|
173
|
-
{mergedConfig.separator && idx < activeItems.length * 2 - 1 && (
|
|
174
|
-
<span style={{ opacity: 0.5, margin: '0 8px' }}>
|
|
175
|
-
{mergedConfig.separator}
|
|
176
|
-
</span>
|
|
177
|
-
)}
|
|
178
|
-
</span>
|
|
179
|
-
))}
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<style>{`
|
|
184
|
-
@keyframes ticker-scroll {
|
|
185
|
-
0% {
|
|
186
|
-
transform: translateX(0);
|
|
187
|
-
}
|
|
188
|
-
100% {
|
|
189
|
-
transform: translateX(-50%);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
`}</style>
|
|
193
|
-
</>
|
|
194
|
-
);
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
export default FlashNewsTicker;
|