diff --git a/services/web-ui/public/screens-library.jsx b/services/web-ui/public/screens-library.jsx
index 045f6f7..4bb4223 100644
--- a/services/web-ui/public/screens-library.jsx
+++ b/services/web-ui/public/screens-library.jsx
@@ -7,14 +7,14 @@ function Library({ navigate, onOpenAsset, openProject }) {
const [search, setSearch] = React.useState('');
let assets = openProject
- ? ALL_ASSETS.filter(a => a.project_id === openProject.id)
+ ? ALL_ASSETS.filter(function(a) { return a.project_id === openProject.id; })
: ALL_ASSETS;
- if (filter !== 'all') assets = assets.filter(a => a.status === filter);
- if (search) assets = assets.filter(a => a.name.toLowerCase().includes(search.toLowerCase()));
+ if (filter !== 'all') assets = assets.filter(function(a) { return a.status === filter; });
+ if (search) assets = assets.filter(function(a) { return a.name.toLowerCase().includes(search.toLowerCase()); });
const displayTitle = openProject ? openProject.name : 'All Assets';
- const errorCount = ALL_ASSETS.filter(a => a.status === 'error').length;
- const recentCount = ALL_ASSETS.filter(a => (Date.now() - new Date(a.created_at)) < 86400000).length;
+ const errorCount = ALL_ASSETS.filter(function(a) { return a.status === 'error'; }).length;
+ const recentCount = ALL_ASSETS.filter(function(a) { return (Date.now() - new Date(a.created_at)) < 86400000; }).length;
return (
@@ -22,41 +22,45 @@ function Library({ navigate, onOpenAsset, openProject }) {
Projects
-
navigate('library')} style={{ cursor: 'pointer' }}>
+
All projects
{ALL_ASSETS.length}
- {PROJECTS.slice(0, 8).map(p => (
-
{ navigate('projects'); }}>
-
- {p.name}
- {p.assets}
-
- ))}
+ {PROJECTS.slice(0, 8).map(function(p) {
+ return (
+
+
+ {p.name}
+ {p.assets}
+
+ );
+ })}
{BINS.length > 0 && (
Bins
- {BINS.map(b => (
-
-
- {b.name}
- {b.count}
-
- ))}
+ {BINS.map(function(b) {
+ return (
+
+
+ {b.name}
+ {b.count}
+
+ );
+ })}
)}
Smart filters
- {errorCount > 0 &&
setFilter('error')} style={{ cursor: 'pointer' }}>Errors{errorCount}
}
-
setFilter('all')} style={{ cursor: 'pointer' }}>Last 24h{recentCount}
-
setFilter('ready')} style={{ cursor: 'pointer' }}>Ready{ALL_ASSETS.filter(a => a.status === 'ready').length}
+ {errorCount > 0 &&
Errors{errorCount}
}
+
Last 24h{recentCount}
+
Ready{ALL_ASSETS.filter(function(a) { return a.status === 'ready'; }).length}
@@ -68,51 +72,55 @@ function Library({ navigate, onOpenAsset, openProject }) {
- setSearch(e.target.value)} placeholder="Filter assets…" />
+
- {['all', 'ready', 'processing', 'live', 'error'].map(f => (
-
- ))}
+ {['all', 'ready', 'processing', 'live', 'error'].map(function(f) {
+ return (
+
+ );
+ })}
-
-
+
+
-
+
{assets.length === 0 ? (
No assets match this filter.
) : view === 'grid' ? (
- {assets.map(a =>
onOpenAsset(a)} />)}
+ {assets.map(function(a) { return ; })}
) : (
Name
Duration
Resolution
Codec
Size
Updated
- {assets.map(a => (
-
onOpenAsset(a)} style={{ cursor: 'pointer' }}>
-
-
-
{a.name}
-
-
-
{a.status}
+ {assets.map(function(a) {
+ return (
+
+
+
+
{a.name}
+
+
+ {a.status}
+
+
{a.duration}
+
{a.res}
+
{a.codec || '—'}
+
{a.size}
+
{a.updated}
+
-
{a.duration}
-
{a.res}
-
{a.codec || '—'}
-
{a.size}
-
{a.updated}
-
-
- ))}
+ );
+ })}
)}
@@ -121,9 +129,66 @@ function Library({ navigate, onOpenAsset, openProject }) {
}
function AssetCard({ asset, onOpen }) {
+ const [hoverStream, setHoverStream] = React.useState(null);
+ const [hovered, setHovered] = React.useState(false);
+ const timerRef = React.useRef(null);
+ const hlsRef = React.useRef(null);
+ const videoRef = React.useRef(null);
+
+ const handleMouseEnter = function() {
+ timerRef.current = setTimeout(function() {
+ setHovered(true);
+ if (!hoverStream && (asset.status === 'ready' || asset.status === 'live')) {
+ window.ZAMPP_API.fetch('/assets/' + asset.id + '/stream')
+ .then(function(r) { if (r && r.url) setHoverStream(r); })
+ .catch(function() {});
+ }
+ }, 350);
+ };
+
+ const handleMouseLeave = function() {
+ clearTimeout(timerRef.current);
+ setHovered(false);
+ };
+
+ // HLS wiring
+ React.useEffect(function() {
+ if (!hovered || !hoverStream || hoverStream.type !== 'hls' || !videoRef.current) return;
+ if (!window.Hls) return;
+ hlsRef.current = new window.Hls({ maxBufferLength: 10 });
+ hlsRef.current.loadSource(hoverStream.url);
+ hlsRef.current.attachMedia(videoRef.current);
+ return function() {
+ if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; }
+ };
+ }, [hovered, hoverStream]);
+
+ const showVideo = hovered && hoverStream;
+
return (
-
-
+
+
+
+ {showVideo && (
+
+ )}
+
{asset.status === 'live' && LIVE}
{asset.status === 'processing' && Processing}