Skip to content

Instantly share code, notes, and snippets.

@j2is
Last active November 9, 2021 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save j2is/569e9db93bb455168231bd1c43939aef to your computer and use it in GitHub Desktop.
Save j2is/569e9db93bb455168231bd1c43939aef to your computer and use it in GitHub Desktop.
Better Sanity Player Barebones
import React, { useRef, useState, useEffect } from "react";
import Hls from "hls.js";
function getPosterSrc(playbackId, options = {}) {
const width = options.width || 640;
const height = options.height || "";
const time = options.time || 0;
const fitMode =
typeof options.fitMode === "undefined" ? "smartcrop" : options.fitMode;
let url = `https://image.mux.com/${playbackId}/thumbnail.png?width=${width}&fit_mode=${fitMode}&time=${time}`;
if (options.height) {
url += `&height=${height}`;
}
return url;
}
export default React.memo(function Video({
autoload = true,
autoplay = false,
className = "",
loop = false,
muted = false,
playsinline,
showControls = false,
poster = true,
settings = {},
assetDocument,
onAttached
}) {
const hls = useRef();
const video = useRef();
const container = useRef();
const [posterUrl, setPosterUrl] = useState();
const [errors, setErrors] = useState();
const [source, setSource] = useState();
const hlsjsDefaults = {
debug: process.env.NODE_ENV !== "production",
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 60 * 1
};
const attachVideo = () => {
settings.autoStartLoad = autoload;
if (Hls.isSupported()) {
const hlsConfig = Object.assign(settings, hlsjsDefaults);
hls.current = new Hls(hlsConfig);
hls.current.loadSource(source);
hls.current.attachMedia(video.current);
hls.current.on(Hls.Events.MANIFEST_PARSED, () => {
if (container.current) {
container.current.style.display = "block";
}
if (onAttached) {
onAttached({ video: video.current });
}
});
hls.current.on(Hls.Events.ERROR, (event, data) => {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
container.current.style.display = "none";
setErrors({ error: data });
break;
case Hls.ErrorTypes.MEDIA_ERROR:
// Don't output anything visible as these mostly are non-fatal
break;
default:
container.current.style.display = "none";
setErrors({ error: data });
}
console.error(data); // eslint-disable-line no-console
});
}
if (video.current.canPlayType("application/vnd.apple.mpegurl")) {
video.current.src = source;
video.current.addEventListener("loadedmetadata", () => {
container.current.style.display = "block";
});
video.current.addEventListener("error", () => {
container.current.style.display = "none";
setErrors({
error: {
type: `${video.current.error.constructor.name} code ${video.current.error.code}`
}
});
});
}
addVideoEventListeners(video.current);
};
function handleVideoEvent(evt) {
switch (evt.type) {
case "error":
data = Math.round(evt.target.currentTime * 1000);
if (evt.type === "error") {
let errorTxt;
const mediaError = evt.currentTarget.error;
switch (mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED:
errorTxt = "You aborted the video playback";
break;
case mediaError.MEDIA_ERR_DECODE:
errorTxt =
"The video playback was aborted due to a corruption problem or because the video used features your browser did not support";
handleMediaError();
break;
case mediaError.MEDIA_ERR_NETWORK:
errorTxt =
"A network error caused the video download to fail part-way";
break;
case mediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
errorTxt =
"The video could not be loaded, either because the server or network failed or because the format is not supported";
break;
}
if (mediaError.message) {
errorTxt += " - " + mediaError.message;
}
//try to recover
hls.current.recoverMediaError()
console.error(errorTxt);
}
break;
default:
break;
}
}
const addVideoEventListeners = elem => {
elem.addEventListener("error", handleVideoEvent);
};
const setup = () => {
if (assetDocument && assetDocument.playbackId) {
if (poster === true) {
const rect = container.current.getBoundingClientRect();
const width = rect.width > 768 ? 640 : 400;
const url = getPosterSrc(assetDocument.playbackId, {
time: assetDocument.thumbTime || 0,
fitMode: "preserve",
width
});
setPosterUrl(url);
}
setSource(`https://stream.mux.com/${assetDocument.playbackId}.m3u8`);
}
};
const destroy = () => {
return new Promise((resolve, reject) => {
setErrors();
if (hls.current) {
hls.current.destroy();
clearInterval(hls.current?.bufferTimer);
hls.current = null;
}
resolve();
});
};
const checkShouldLoad = () => {
if (source && video.current && !video.current.src) {
attachVideo();
return;
}
destroy();
};
useEffect(() => {
setup();
}, []);
useEffect(() => {
checkShouldLoad();
}, [source]);
useEffect(() => {
if (errors) {
console.error(errors);
}
}, [errors]);
return (
<div
ref={container}
className={`${className} video-container`}
style={style}
>
<video
controls={showControls}
playsInline={playsinline}
muted={autoplay || muted}
autoPlay={autoplay}
ref={video}
poster={posterUrl}
loop={loop}
></video>
</div>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment