mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-03 09:45:22 +03:00
Add support for mse
This commit is contained in:
parent
9ac69feab7
commit
abbc4d216b
93
web/src/components/MsePlayer.jsx
Normal file
93
web/src/components/MsePlayer.jsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { baseUrl } from '../api/baseUrl';
|
||||||
|
import { useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
|
export default function MsePlayer({ camera, width, height }) {
|
||||||
|
const url = `${baseUrl.replace(/^http/, 'ws')}live/webrtc/api/ws?src=${camera}`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const video = document.querySelector('#video');
|
||||||
|
|
||||||
|
// support api_path
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
ws.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
let mediaSource;
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
// https://web.dev/i18n/en/fast-playback-with-preload/#manual_buffering
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API
|
||||||
|
mediaSource = new MediaSource();
|
||||||
|
video.src = URL.createObjectURL(mediaSource);
|
||||||
|
mediaSource.onsourceopen = () => {
|
||||||
|
mediaSource.onsourceopen = null;
|
||||||
|
URL.revokeObjectURL(video.src);
|
||||||
|
ws.send(JSON.stringify({ type: 'mse' }));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let sourceBuffer,
|
||||||
|
queueBuffer = [];
|
||||||
|
|
||||||
|
ws.onmessage = (ev) => {
|
||||||
|
if (typeof ev.data === 'string') {
|
||||||
|
const data = JSON.parse(ev.data);
|
||||||
|
|
||||||
|
if (data.type === 'mse') {
|
||||||
|
sourceBuffer = mediaSource.addSourceBuffer(data.value);
|
||||||
|
// important: segments supports TrackFragDecodeTime
|
||||||
|
// sequence supports only TrackFragRunEntry Duration
|
||||||
|
sourceBuffer.mode = 'segments';
|
||||||
|
sourceBuffer.onupdateend = () => {
|
||||||
|
if (!sourceBuffer.updating && queueBuffer.length > 0) {
|
||||||
|
sourceBuffer.appendBuffer(queueBuffer.shift());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (sourceBuffer.updating) {
|
||||||
|
queueBuffer.push(ev.data);
|
||||||
|
} else {
|
||||||
|
sourceBuffer.appendBuffer(ev.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let offsetTime = 1,
|
||||||
|
noWaiting = 0;
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (video.paused || video.seekable.length === 0) return;
|
||||||
|
|
||||||
|
if (noWaiting < 0) {
|
||||||
|
offsetTime = Math.min(offsetTime * 1.1, 5);
|
||||||
|
} else if (noWaiting >= 30) {
|
||||||
|
noWaiting = 0;
|
||||||
|
offsetTime = Math.max(offsetTime * 0.9, 0.5);
|
||||||
|
}
|
||||||
|
noWaiting += 1;
|
||||||
|
|
||||||
|
const endTime = video.seekable.end(video.seekable.length - 1);
|
||||||
|
let playbackRate = (endTime - video.currentTime) / offsetTime;
|
||||||
|
if (playbackRate < 0.1) {
|
||||||
|
// video.currentTime = endTime - offsetTime;
|
||||||
|
playbackRate = 0.1;
|
||||||
|
} else if (playbackRate > 10) {
|
||||||
|
// video.currentTime = endTime - offsetTime;
|
||||||
|
playbackRate = 10;
|
||||||
|
}
|
||||||
|
// https://github.com/GoogleChrome/developer.chrome.com/issues/135
|
||||||
|
video.playbackRate = playbackRate;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
video.onwaiting = () => {
|
||||||
|
const endTime = video.seekable.end(video.seekable.length - 1);
|
||||||
|
video.currentTime = endTime - offsetTime;
|
||||||
|
noWaiting = -1;
|
||||||
|
};
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<video id="video" autoplay playsinline controls muted width={width} height={height} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ import { useApiHost } from '../api';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import VideoPlayer from '../components/VideoPlayer';
|
import VideoPlayer from '../components/VideoPlayer';
|
||||||
import WebRtcPlayer from '../components/WebRtcPlayer';
|
import WebRtcPlayer from '../components/WebRtcPlayer';
|
||||||
|
import MsePlayer from '../components/MsePlayer';
|
||||||
|
|
||||||
const emptyObject = Object.freeze({});
|
const emptyObject = Object.freeze({});
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ export default function Camera({ camera }) {
|
|||||||
? Math.round(cameraConfig.restream.jsmpeg.height * (cameraConfig.detect.width / cameraConfig.detect.height))
|
? Math.round(cameraConfig.restream.jsmpeg.height * (cameraConfig.detect.width / cameraConfig.detect.height))
|
||||||
: 0;
|
: 0;
|
||||||
const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(`${camera}-source`, 'jsmpeg');
|
const [viewSource, setViewSource, sourceIsLoaded] = usePersistence(`${camera}-source`, 'jsmpeg');
|
||||||
const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['jsmpeg', 'mp4', 'webrtc'] : ['jsmpeg'];
|
const sourceValues = cameraConfig && cameraConfig.restream.enabled ? ['jsmpeg', 'mp4', 'mse', 'webrtc'] : ['jsmpeg'];
|
||||||
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
|
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
|
||||||
|
|
||||||
const handleSetOption = useCallback(
|
const handleSetOption = useCallback(
|
||||||
@ -116,11 +117,17 @@ export default function Camera({ camera }) {
|
|||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
seekOptions={{ forward: false, back: false }}
|
seekOptions={{ forward: false, back: false }}
|
||||||
onReady={() => {}}
|
onReady={() => { }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
} else if (viewSource == 'mse') {
|
||||||
|
player = (
|
||||||
|
<Fragment>
|
||||||
|
<MsePlayer camera={camera} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
} else if (viewSource == 'webrtc') {
|
} else if (viewSource == 'webrtc') {
|
||||||
player = (
|
player = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user