-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Web player frontend - still lots of bugs and very finicky, but almost…
… there.
- Loading branch information
1 parent
6590193
commit b8d46b4
Showing
12 changed files
with
216 additions
and
99 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,70 @@ | ||
import { Button } from '@mui/material'; | ||
import Hls from 'hls.js'; | ||
import { useCallback, useEffect, useRef } from 'react'; | ||
import { useEffect, useMemo, useRef, useState } from 'react'; | ||
import { useBlocker } from 'react-router-dom'; | ||
import { apiClient } from '../external/api.ts'; | ||
import { useHls } from '../hooks/useHls.ts'; | ||
|
||
export default function Video() { | ||
type VideoProps = { | ||
channelNumber: number; | ||
}; | ||
|
||
export default function Video({ channelNumber }: VideoProps) { | ||
const videoRef = useRef<HTMLVideoElement | null>(null); | ||
const hlsRef = useRef<Hls | null>(null); | ||
const { hls, resetHls } = useHls(); | ||
const hlsSupported = useMemo(() => Hls.isSupported(), []); | ||
const [loadedStream, setLoadedStream] = useState(false); | ||
|
||
useEffect(() => { | ||
const hls = hlsRef.current; | ||
if (hls) { | ||
hls.on(Hls.Events.ERROR, (_, data) => { | ||
console.error('HLS error', data); | ||
}); | ||
} | ||
}, [hlsRef]); | ||
const blocker = useBlocker( | ||
({ currentLocation, nextLocation }) => | ||
currentLocation.pathname !== nextLocation.pathname, | ||
); | ||
|
||
// Unload HLS when navigating away | ||
useEffect(() => { | ||
const hls = hlsRef.current; | ||
const video = videoRef.current; | ||
if (hls && video && !hls.media) { | ||
hls.attachMedia(video); | ||
if (blocker.state === 'blocked') { | ||
if (videoRef.current) { | ||
console.log('Pause'); | ||
videoRef.current.pause(); | ||
} | ||
if (hls) { | ||
console.log('stopping playback'); | ||
hls.detachMedia(); | ||
hls.destroy(); | ||
} | ||
blocker.proceed(); | ||
} | ||
}, [hlsRef, videoRef]); | ||
}, [blocker, hls, videoRef]); | ||
|
||
const loadHls = useCallback(() => { | ||
useEffect(() => { | ||
console.info('Loading stream...'); | ||
const video = videoRef.current; | ||
const hls = hlsRef.current; | ||
if (video && hls) { | ||
if (video && hls && !loadedStream) { | ||
setLoadedStream(true); | ||
apiClient | ||
.startHlsStream({ params: { channelNumber: 1 } }) | ||
.startHlsStream({ params: { channelNumber } }) | ||
.then(({ streamPath }) => { | ||
console.log('Got stream', streamPath, hls); | ||
hls.loadSource(`http://localhost:8000${streamPath}`); | ||
if (!hls.media) { | ||
hls.attachMedia(video); | ||
} | ||
video.play().catch(console.error); | ||
hls.attachMedia(video); | ||
}) | ||
.catch((err) => console.error('Unable to fetch stream URL', err)); | ||
.catch((err) => { | ||
console.error('Unable to fetch stream URL', err); | ||
setLoadedStream(false); | ||
}); | ||
} | ||
}, [videoRef, hlsRef]); | ||
}, [videoRef, hls, loadedStream, channelNumber]); | ||
|
||
if (!Hls.isSupported()) { | ||
return ( | ||
<div> | ||
HLS not supported in this browser - we won't be able to play streams. | ||
</div> | ||
); | ||
} else { | ||
hlsRef.current = new Hls({ | ||
progressive: true, | ||
fragLoadingTimeOut: 30000, | ||
initialLiveManifestSize: 3, // About 10 seconds of playback needed before playing | ||
enableWorker: true, | ||
lowLatencyMode: true, | ||
xhrSetup: (xhr) => { | ||
xhr.setRequestHeader( | ||
'Access-Control-Allow-Headers', | ||
'Content-Type, Accept, X-Requested-With', | ||
); | ||
xhr.setRequestHeader( | ||
'Access-Control-Allow-Origin', | ||
'http://localhost:5173', | ||
); | ||
}, | ||
}); | ||
} | ||
useEffect(() => { | ||
console.log('channel number change, reload'); | ||
resetHls(); | ||
setLoadedStream(false); | ||
}, [channelNumber, resetHls]); | ||
|
||
return ( | ||
return !hlsSupported ? ( | ||
<div>HLS not supported in this browser!</div> | ||
) : ( | ||
<div> | ||
<Button onClick={loadHls}>Load HLS</Button> | ||
<video style={{ width: '1080px' }} controls ref={videoRef} /> | ||
<video style={{ width: '1080px' }} controls autoPlay ref={videoRef} /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import Hls from 'hls.js'; | ||
import { useCallback, useEffect, useRef } from 'react'; | ||
|
||
const hlsSupported = Hls.isSupported(); | ||
|
||
export const useHls = () => { | ||
const hlsRef = useRef<Hls | null>(null); | ||
|
||
const refreshHls = () => { | ||
if (!hlsSupported) { | ||
return; | ||
} | ||
|
||
console.log('Initializing HLS'); | ||
|
||
const newHls = new Hls({ | ||
progressive: true, | ||
fragLoadingTimeOut: 30000, | ||
initialLiveManifestSize: 3, // About 10 seconds of playback needed before playing | ||
enableWorker: true, | ||
lowLatencyMode: true, | ||
xhrSetup: (xhr) => { | ||
xhr.setRequestHeader( | ||
'Access-Control-Allow-Headers', | ||
'Content-Type, Accept, X-Requested-With', | ||
); | ||
xhr.setRequestHeader( | ||
'Access-Control-Allow-Origin', | ||
'http://localhost:5173', | ||
); | ||
}, | ||
}); | ||
|
||
newHls.on(Hls.Events.MANIFEST_PARSED, function (_, data) { | ||
console.debug( | ||
'manifest loaded, found ' + data.levels.length + ' quality level', | ||
); | ||
}); | ||
|
||
newHls.on(Hls.Events.ERROR, (_, data) => { | ||
console.error('HLS error', data); | ||
}); | ||
|
||
newHls.on(Hls.Events.MEDIA_ATTACHED, function () { | ||
console.debug('video and hls.js are now bound together !'); | ||
}); | ||
|
||
hlsRef.current = newHls; | ||
}; | ||
|
||
const resetHls = useCallback(() => { | ||
if (hlsRef.current) { | ||
hlsRef.current.destroy(); | ||
} | ||
hlsRef.current = null; | ||
refreshHls(); | ||
}, [hlsRef]); | ||
|
||
useEffect(() => { | ||
refreshHls(); | ||
}, []); | ||
|
||
return { | ||
hls: hlsRef.current, | ||
resetHls, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.