2309e9f43a
Five-service streaming platform: auth, catalogue, streaming, ingest, thumbnailer. Includes React frontend served by nginx, NATS JetStream event bus, aiobotocore async S3, PyAV video metadata + thumbnail extraction, service-to-service JWT auth, and a full unit + e2e test suite.
42 lines
1.1 KiB
React
42 lines
1.1 KiB
React
import { useEffect, useRef, useState } from 'react'
|
|
import { getStreamToken } from '../api'
|
|
|
|
export default function VideoPlayer({ item, jwt, onClose }) {
|
|
const [src, setSrc] = useState(null)
|
|
const [error, setError] = useState(null)
|
|
const videoRef = useRef(null)
|
|
|
|
useEffect(() => {
|
|
getStreamToken(item.id, jwt)
|
|
.then(url => setSrc(url))
|
|
.catch(e => setError(e.message))
|
|
}, [item.id, jwt])
|
|
|
|
useEffect(() => {
|
|
if (src && videoRef.current) {
|
|
videoRef.current.play().catch(() => {})
|
|
videoRef.current.requestFullscreen?.().catch(() => {})
|
|
}
|
|
}, [src])
|
|
|
|
return (
|
|
<div className="player-overlay" onClick={onClose}>
|
|
<div className="player-box" onClick={e => e.stopPropagation()}>
|
|
<button className="player-close" onClick={onClose}>✕</button>
|
|
<h2>{item.title}</h2>
|
|
{error && <p className="error">{error}</p>}
|
|
{src ? (
|
|
<video
|
|
ref={videoRef}
|
|
src={src}
|
|
controls
|
|
className="player-video"
|
|
/>
|
|
) : (
|
|
!error && <p>Loading…</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|