Try webrtc when mse fails with decoding error (#11745)

* Try webrtc if enabled and mse fails with decoding error

* default to jsmpeg if webrtc times out

* check for mic first
This commit is contained in:
Josh Hawkins 2024-06-04 10:11:32 -05:00 committed by GitHub
parent 2875e84cb5
commit 3f0a954856
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 22 deletions

View File

@ -138,6 +138,7 @@ export default function LivePlayer({
iOSCompatFullScreen={iOSCompatFullScreen} iOSCompatFullScreen={iOSCompatFullScreen}
onPlaying={() => setLiveReady(true)} onPlaying={() => setLiveReady(true)}
pip={pip} pip={pip}
onError={onError}
/> />
); );
} else if (liveMode == "mse") { } else if (liveMode == "mse") {

View File

@ -8,6 +8,7 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { isIOS, isSafari } from "react-device-detect";
type MSEPlayerProps = { type MSEPlayerProps = {
camera: string; camera: string;
@ -311,9 +312,11 @@ function MSEPlayer({
onPlaying?.(); onPlaying?.();
}} }}
muted={!audioEnabled} muted={!audioEnabled}
onProgress={ onProgress={() => {
onError != undefined if (isSafari || isIOS) {
? () => { onPlaying?.();
}
if (onError != undefined) {
if (videoRef.current?.paused) { if (videoRef.current?.paused) {
return; return;
} }
@ -329,8 +332,7 @@ function MSEPlayer({
}, 3000), }, 3000),
); );
} }
: undefined }}
}
onError={(e) => { onError={(e) => {
if ( if (
// @ts-expect-error code does exist // @ts-expect-error code does exist
@ -339,6 +341,16 @@ function MSEPlayer({
onError?.("startup"); onError?.("startup");
} }
if (
// @ts-expect-error code does exist
e.target.error.code == MediaError.MEDIA_ERR_DECODE &&
(isSafari || isIOS)
) {
onError?.("mse-decode");
clearTimeout(bufferTimeout);
setBufferTimeout(undefined);
}
if (wsRef.current) { if (wsRef.current) {
wsRef.current.close(); wsRef.current.close();
wsRef.current = null; wsRef.current = null;

View File

@ -36,6 +36,7 @@ export default function WebRtcPlayer({
const pcRef = useRef<RTCPeerConnection | undefined>(); const pcRef = useRef<RTCPeerConnection | undefined>();
const videoRef = useRef<HTMLVideoElement | null>(null); const videoRef = useRef<HTMLVideoElement | null>(null);
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>(); const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
const videoLoadTimeoutRef = useRef<NodeJS.Timeout>();
const PeerConnection = useCallback( const PeerConnection = useCallback(
async (media: string) => { async (media: string) => {
@ -193,6 +194,27 @@ export default function WebRtcPlayer({
videoRef.current.requestPictureInPicture(); videoRef.current.requestPictureInPicture();
}, [pip, videoRef]); }, [pip, videoRef]);
useEffect(() => {
videoLoadTimeoutRef.current = setTimeout(() => {
onError?.("stalled");
}, 5000);
return () => {
if (videoLoadTimeoutRef.current) {
clearTimeout(videoLoadTimeoutRef.current);
}
};
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleLoadedData = () => {
if (videoLoadTimeoutRef.current) {
clearTimeout(videoLoadTimeoutRef.current);
}
onPlaying?.();
};
return ( return (
<video <video
ref={videoRef} ref={videoRef}
@ -201,7 +223,7 @@ export default function WebRtcPlayer({
autoPlay autoPlay
playsInline playsInline
muted={!audioEnabled} muted={!audioEnabled}
onLoadedData={onPlaying} onLoadedData={handleLoadedData}
onProgress={ onProgress={
onError != undefined onError != undefined
? () => { ? () => {

View File

@ -31,4 +31,4 @@ export type LiveStreamMetadata = {
consumers: LiveConsumerMetadata[]; consumers: LiveConsumerMetadata[];
}; };
export type LivePlayerError = "stalled" | "startup"; export type LivePlayerError = "stalled" | "startup" | "mse-decode";

View File

@ -190,6 +190,7 @@ export default function LiveCameraView({
const [audio, setAudio] = useState(false); const [audio, setAudio] = useState(false);
const [mic, setMic] = useState(false); const [mic, setMic] = useState(false);
const [webRTC, setWebRTC] = useState(false);
const [pip, setPip] = useState(false); const [pip, setPip] = useState(false);
const [lowBandwidth, setLowBandwidth] = useState(false); const [lowBandwidth, setLowBandwidth] = useState(false);
@ -203,12 +204,20 @@ export default function LiveCameraView({
return "webrtc"; return "webrtc";
} }
if (webRTC && isRestreamed) {
return "webrtc";
}
if (webRTC && !isRestreamed) {
return "jsmpeg";
}
if (lowBandwidth) { if (lowBandwidth) {
return "jsmpeg"; return "jsmpeg";
} }
return "mse"; return "mse";
}, [lowBandwidth, mic]); }, [lowBandwidth, mic, webRTC, isRestreamed]);
// layout state // layout state
@ -426,7 +435,14 @@ export default function LiveCameraView({
pip={pip} pip={pip}
setFullResolution={setFullResolution} setFullResolution={setFullResolution}
containerRef={containerRef} containerRef={containerRef}
onError={() => setLowBandwidth(true)} onError={(e) => {
if (e == "mse-decode") {
setWebRTC(true);
} else {
setWebRTC(false);
setLowBandwidth(true);
}
}}
/> />
</div> </div>
{camera.onvif.host != "" && ( {camera.onvif.host != "" && (